Compare commits

..

30 Commits

Author SHA1 Message Date
2023795f3c fix: token endpoint supports json format (#641)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-03 21:32:00 +08:00
8d13bf7e27 feat: add Alipay support as idp (#638)
* feat: add alipay support as idp

* fix: rename a static svg icon

* fix: sort imports

* fix: no longer use pkcs8 package
2022-04-02 22:37:13 +08:00
29aa379fb2 fix: qq idp missing username (#636)
* fix: qq idp missing username

Signed-off-by: Steve0x2a <stevesough@gmail.com>

* fix: api uses the latest fields

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-01 11:46:33 +08:00
7a95b9c1d5 Init DB only when necessary. 2022-03-31 12:28:45 +08:00
0fc0ba0c76 feat: support global admin to modify the email and phone of other users (#633)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-30 20:27:23 +08:00
24459d852e fix: comparing hashed password with plain text password during password grant (#627)
* fix: use object.CheckPassword for password grant

* Apply suggestions from code review

fix: remove log per change request
2022-03-30 00:37:38 +08:00
e3f5bf93b2 fix: adjust the password check logic for ldap user (#597)
* fix: the password check logic for ldap user.
LDAP user should only use the ldap connection to check the password.

* fix: code format
2022-03-28 17:19:58 +08:00
879ca6a488 fix: refresh_token api return old token (#623)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-27 23:10:05 +08:00
544cd40a08 Disable the new syncer by default. 2022-03-27 23:06:52 +08:00
99f7883c7d Fix null bug in getCountryRegionData(). 2022-03-27 16:03:25 +08:00
88b0fb6e52 Add getPrice(). 2022-03-26 16:42:25 +08:00
fa9b49e25b fix: some idp error messages return unclear (#620)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-26 15:15:56 +08:00
cd76e9372e feat: delete the old token when refreshing token (#617)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-24 19:58:12 +08:00
04b9e05244 fix: WeComInternalIdProvider GetUserInfo method could not get the correct user id (#616) 2022-03-24 17:53:05 +08:00
a78b2de7b2 fix: panic when not select one provider (#614)
Signed-off-by: Sagilio <Sagilio@outlook.com>
2022-03-24 12:15:10 +08:00
d0952ae908 fix: docker-compose up can't work on linux (#606) 2022-03-22 18:43:02 +08:00
ade64693e4 fix: support lower go version(1.15) (#599)
* fix: support lower go version(1.15)

* fix: support lower go version(1.15)

* fix: support lower go version(1.15)
2022-03-21 21:55:16 +08:00
5f8924ed4e feat: support overriding configuration with env (#590) 2022-03-20 23:21:09 +08:00
1a6d98d029 refactor: New Crowdin translations by Github Action (#592)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-20 22:30:29 +08:00
447dd1c534 feat: update the uploaded user field and provide demo xlsx file (#596)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 22:28:22 +08:00
86b5d72e5d fix: concatChar assignment logic (#595)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 11:54:14 +08:00
6bc4e646e5 fix: oAuthParams may not exist (#594)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 10:33:50 +08:00
0841eb5c30 Fix !skipCi directive. 2022-03-19 23:15:19 +08:00
4015c221f7 refactor: New Crowdin translations by Github Action (#588)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-19 22:01:20 +08:00
dcd6328498 fix: callback url param missing (#583)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-19 20:01:44 +08:00
8080927890 fix: redirect for non-built-in app logout (#587)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-19 19:50:05 +08:00
a95c5b05a9 Remove GitHub provider hacking code. 2022-03-19 19:43:54 +08:00
865a65d399 fix: fix the params problem in code signin (#577)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-18 20:12:29 +08:00
e8b9c67671 feat: add casdoor as itself idp support (#578)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-18 18:28:46 +08:00
e5ff49f7a7 fix: UI bug after switching to English (#570) 2022-03-15 21:02:54 +08:00
70 changed files with 1469 additions and 228 deletions

View File

@ -15,7 +15,6 @@
package authz
import (
"github.com/astaxie/beego"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2"
@ -28,8 +27,8 @@ var Enforcer *casbin.Enforcer
func InitAuthz() {
var err error
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true)
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}

View File

@ -15,14 +15,49 @@
package conf
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/astaxie/beego"
)
func GetConfigString(key string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return beego.AppConfig.String(key)
}
func GetConfigBool(key string) (bool, error) {
value := GetConfigString(key)
if value == "true" {
return true, nil
} else if value == "false" {
return false, nil
}
return false, fmt.Errorf("value %s cannot be converted into bool", value)
}
func GetConfigInt64(key string) (int64, error) {
value := GetConfigString(key)
num, err := strconv.ParseInt(value, 10, 64)
return num, err
}
func init() {
//this array contains the beego configuration items that may be modified via env
var presetConfigItems = []string{"httpport", "appname"}
for _, key := range presetConfigItems {
if value, ok := os.LookupEnv(key); ok {
beego.AppConfig.Set(key, value)
}
}
}
func GetBeegoConfDataSourceName() string {
dataSourceName := beego.AppConfig.String("dataSourceName")
dataSourceName := GetConfigString("dataSourceName")
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
if runningInDocker == "true" {

98
conf/conf_test.go Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conf
import (
"os"
"testing"
"github.com/astaxie/beego"
"github.com/stretchr/testify/assert"
)
func TestGetConfString(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"Should be return casbin", "appname", "casbin"},
{"Should be return 8000", "httpport", "8000"},
{"Should be return value", "key", "value"},
}
//do some set up job
os.Setenv("appname", "casbin")
os.Setenv("key", "value")
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual := GetConfigString(scenery.input)
assert.Equal(t, scenery.expected, actual)
})
}
}
func TestGetConfInt(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"Should be return 8000", "httpport", 8001},
{"Should be return 8000", "verificationCodeTimeout", 10},
}
//do some set up job
os.Setenv("httpport", "8001")
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual, err := GetConfigInt64(scenery.input)
assert.Nil(t, err)
assert.Equal(t, scenery.expected, int(actual))
})
}
}
func TestGetConfBool(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"Should be return false", "SessionOn", false},
{"Should be return false", "copyrequestbody", true},
}
//do some set up job
os.Setenv("SessionOn", "false")
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual, err := GetConfigBool(scenery.input)
assert.Nil(t, err)
assert.Equal(t, scenery.expected, actual)
})
}
}

View File

@ -225,10 +225,15 @@ func (c *ApiController) Logout() {
user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
c.SetSessionUsername("")
c.SetSessionData(nil)
c.ResponseOk(user)
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
c.ResponseOk(user)
return
}
c.ResponseOk(user, application.HomepageUrl)
}
// GetAccount

View File

@ -23,7 +23,7 @@ import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
@ -267,8 +267,8 @@ func (c *ApiController) Login() {
setHttpClient(idProvider, provider.Type)
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State))
if form.State != conf.GetConfigString("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", conf.GetConfigString("authState"), form.State))
return
}

View File

@ -72,6 +72,15 @@ func (c *ApiController) GetSessionUsername() string {
return user.(string)
}
func (c *ApiController) GetSessionApplication() *object.Application {
clientId := c.GetSession("aud")
if clientId == nil {
return nil
}
application := object.GetApplicationByClientId(clientId.(string))
return application
}
func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData()
if sessionData != nil &&

View File

@ -178,7 +178,7 @@ func (c *ApiController) UpdateLdap() {
}
if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0{
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
}

View File

@ -179,6 +179,20 @@ func (c *ApiController) GetOAuthToken() {
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
code = tokenRequest.Code
verifier = tokenRequest.Verifier
scope = tokenRequest.Scope
username = tokenRequest.Username
password = tokenRequest.Password
}
}
host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host)
@ -204,6 +218,18 @@ func (c *ApiController) RefreshToken() {
clientSecret := c.Input().Get("client_secret")
host := c.Ctx.Request.Host
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
scope = tokenRequest.Scope
}
}
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
c.ServeJSON()
}

26
controllers/types.go Normal file
View File

@ -0,0 +1,26 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
type TokenRequest struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Verifier string `json:"code_verifier"`
Scope string `json:"scope"`
Username string `json:"username"`
Password string `json:"password"`
}

View File

@ -18,7 +18,7 @@ import (
"fmt"
"strconv"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -62,7 +62,7 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
}
func getInitScore() int {
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
score, err := strconv.Atoi(conf.GetConfigString("initScore"))
if err != nil {
panic(err)
}

View File

@ -11,6 +11,8 @@ services:
- db
environment:
RUNNING_IN_DOCKER: "true"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./conf:/conf/
db:

1
go.sum
View File

@ -381,6 +381,7 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220208233918-bba287dce954 h1:BkypuErRT9A9I/iljuaG3/zdMjd/J6m8tKKJQtGfSdA=

View File

@ -19,7 +19,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
@ -88,7 +88,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

292
idp/alipay.go Normal file
View File

@ -0,0 +1,292 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"io"
"io/ioutil"
"net/http"
"net/url"
"sort"
"strings"
"time"
"golang.org/x/oauth2"
)
type AlipayIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
// NewAlipayIdProvider ...
func NewAlipayIdProvider(clientId string, clientSecret string, redirectUrl string) *AlipayIdProvider {
idp := &AlipayIdProvider{}
config := idp.getConfig(clientId, clientSecret, redirectUrl)
idp.Config = config
return idp
}
// SetHttpClient ...
func (idp *AlipayIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *AlipayIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
AuthURL: "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm",
TokenURL: "https://openapi.alipay.com/gateway.do",
}
var config = &oauth2.Config{
Scopes: []string{"", ""},
Endpoint: endpoint,
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
}
return config
}
type AlipayAccessToken struct {
Response AlipaySystemOauthTokenResponse `json:"alipay_system_oauth_token_response"`
Sign string `json:"sign"`
}
type AlipaySystemOauthTokenResponse struct {
AccessToken string `json:"access_token"`
AlipayUserId string `json:"alipay_user_id"`
ExpiresIn int `json:"expires_in"`
ReExpiresIn int `json:"re_expires_in"`
RefreshToken string `json:"refresh_token"`
UserId string `json:"user_id"`
}
// GetToken use code to get access_token
func (idp *AlipayIdProvider) GetToken(code string) (*oauth2.Token, error) {
pTokenParams := &struct {
ClientId string `json:"app_id"`
CharSet string `json:"charset"`
Code string `json:"code"`
GrantType string `json:"grant_type"`
Method string `json:"method"`
SignType string `json:"sign_type"`
TimeStamp string `json:"timestamp"`
Version string `json:"version"`
}{idp.Config.ClientID, "utf-8", code, "authorization_code", "alipay.system.oauth.token", "RSA2", time.Now().Format("2006-01-02 15:04:05"), "1.0"}
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
if err != nil {
return nil, err
}
pToken := &AlipayAccessToken{}
err = json.Unmarshal(data, pToken)
if err != nil {
return nil, err
}
token := &oauth2.Token{
AccessToken: pToken.Response.AccessToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.Response.ExpiresIn), 0),
}
return token, nil
}
/*
{
"alipay_user_info_share_response":{
"code":"10000",
"msg":"Success",
"avatar":"https:\/\/tfs.alipayobjects.com\/images\/partner\/T1.QxFXk4aXXXXXXXX",
"nick_name":"zhangsan",
"user_id":"2099222233334444"
},
"sign":"m8rWJeqfoa5tDQRRVnPhRHcpX7NZEgjIPTPF1QBxos6XXXXXXXXXXXXXXXXXXXXXXXXXX"
}
*/
type AlipayUserResponse struct {
AlipayUserInfoShareResponse AlipayUserInfoShareResponse `json:"alipay_user_info_share_response"`
Sign string `json:"sign"`
}
type AlipayUserInfoShareResponse struct {
Code string `json:"code"`
Msg string `json:"msg"`
Avatar string `json:"avatar"`
NickName string `json:"nick_name"`
UserId string `json:"user_id"`
}
// GetUserInfo Use access_token to get UserInfo
func (idp *AlipayIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
atUserInfo := &AlipayUserResponse{}
accessToken := token.AccessToken
pTokenParams := &struct {
ClientId string `json:"app_id"`
CharSet string `json:"charset"`
AuthToken string `json:"auth_token"`
Method string `json:"method"`
SignType string `json:"sign_type"`
TimeStamp string `json:"timestamp"`
Version string `json:"version"`
}{idp.Config.ClientID, "utf-8", accessToken, "alipay.user.info.share", "RSA2", time.Now().Format("2006-01-02 15:04:05"), "1.0"}
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, atUserInfo)
if err != nil {
return nil, err
}
userInfo := UserInfo{
Id: atUserInfo.AlipayUserInfoShareResponse.UserId,
Username: atUserInfo.AlipayUserInfoShareResponse.NickName,
DisplayName: atUserInfo.AlipayUserInfoShareResponse.NickName,
AvatarUrl: atUserInfo.AlipayUserInfoShareResponse.Avatar,
}
return &userInfo, nil
}
func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([]byte, error) {
bs, err := json.Marshal(body)
if err != nil {
return nil, err
}
bodyJson := make(map[string]interface{})
err = json.Unmarshal(bs, &bodyJson)
if err != nil {
return nil, err
}
formData := url.Values{}
for k := range bodyJson {
formData.Set(k, bodyJson[k].(string))
}
sign, err := rsaSignWithRSA256(getStringToSign(formData), idp.Config.ClientSecret)
if err != nil {
return nil, err
}
formData.Set("sign", sign)
resp, err := idp.Client.PostForm(targetUrl, formData)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
return data, nil
}
// get the string to sign, see https://opendocs.alipay.com/common/02kf5q
func getStringToSign(formData url.Values) string {
keys := make([]string, 0, len(formData))
for k := range formData {
keys = append(keys, k)
}
sort.Strings(keys)
str := ""
for _, k := range keys {
if k == "sign" || formData[k][0] == "" {
continue
} else {
str += "&" + k + "=" + formData[k][0]
}
}
str = strings.Trim(str, "&")
return str
}
// use privateKey to sign the content
func rsaSignWithRSA256(signContent string, privateKey string) (string, error) {
privateKey = formatPrivateKey(privateKey)
block, _ := pem.Decode([]byte(privateKey))
if block == nil {
panic("fail to parse privateKey")
}
h := sha256.New()
h.Write([]byte(signContent))
hashed := h.Sum(nil)
privateKeyRSA, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return "", err
}
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKeyRSA.(*rsa.PrivateKey), crypto.SHA256, hashed)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(signature), nil
}
// privateKey in database is a string, format it to PEM style
func formatPrivateKey(privateKey string) string {
// each line length is 64
preFmtPrivateKey := ""
for i := 0; ; {
if i+64 <= len(privateKey) {
preFmtPrivateKey = preFmtPrivateKey + privateKey[i:i+64] + "\n"
i += 64
} else {
preFmtPrivateKey = preFmtPrivateKey + privateKey[i:]
break
}
}
privateKey = strings.Trim(preFmtPrivateKey, "\n")
// add pkcs#8 BEGIN and END
PemBegin := "-----BEGIN PRIVATE KEY-----\n"
PemEnd := "\n-----END PRIVATE KEY-----"
if !strings.HasPrefix(privateKey, PemBegin) {
privateKey = PemBegin + privateKey
}
if !strings.HasSuffix(privateKey, PemEnd) {
privateKey = privateKey + PemEnd
}
return privateKey
}

View File

@ -18,7 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@ -97,7 +97,7 @@ func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

158
idp/casdoor.go Normal file
View File

@ -0,0 +1,158 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"golang.org/x/oauth2"
)
type CasdoorIdProvider struct {
Client *http.Client
Config *oauth2.Config
Host string
}
func NewCasdoorIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *CasdoorIdProvider {
idp := &CasdoorIdProvider{}
config := idp.getConfig(hostUrl)
config.ClientID = clientId
config.ClientSecret = clientSecret
config.RedirectURL = redirectUrl
idp.Config = config
idp.Host = hostUrl
return idp
}
func (idp *CasdoorIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *CasdoorIdProvider) getConfig(hostUrl string) *oauth2.Config {
return &oauth2.Config{
Endpoint: oauth2.Endpoint{
TokenURL: hostUrl + "/api/login/oauth/access_token",
},
Scopes: []string{"openid email profile"},
}
}
type CasdoorToken struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
resp, err := http.PostForm(idp.Config.Endpoint.TokenURL, url.Values{
"client_id": {idp.Config.ClientID},
"client_secret": {idp.Config.ClientSecret},
"code": {code},
"grant_type": {"authorization_code"},
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
pToken := &CasdoorToken{}
err = json.Unmarshal(body, pToken)
if err != nil {
return nil, err
}
//check if token is expired
if pToken.ExpiresIn <= 0 {
return nil, fmt.Errorf("%s", pToken.AccessToken)
}
token := &oauth2.Token{
AccessToken: pToken.AccessToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
}
return token, nil
}
/*
{
"sub": "2f80c349-4beb-407f-b1f0-528aac0f1acd",
"iss": "https://door.casbin.com",
"aud": "7a11****0fa2172",
"name": "admin",
"preferred_username": "Admin",
"email": "admin@example.com",
"picture": "https://casbin.org/img/casbin.svg",
"address": "Guangdong",
"phone": "12345678910"
}
*/
type CasdoorUserInfo struct {
Id string `json:"sub"`
Name string `json:"name"`
DisplayName string `json:"preferred_username"`
Email string `json:"email"`
AvatarUrl string `json:"picture"`
Status string `json:"status"`
Msg string `json:"msg"`
}
func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
cdUserinfo := &CasdoorUserInfo{}
accessToken := token.AccessToken
request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/userinfo", idp.Host), nil)
if err != nil {
return nil, err
}
//add accesstoken to bearer token
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
resp, err := idp.Client.Do(request)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, cdUserinfo)
if err != nil {
return nil, err
}
if cdUserinfo.Status != "" {
return nil, fmt.Errorf("err: %s", cdUserinfo.Msg)
}
userInfo := &UserInfo{
Id: cdUserinfo.Id,
Username: cdUserinfo.Name,
DisplayName: cdUserinfo.DisplayName,
Email: cdUserinfo.Email,
AvatarUrl: cdUserinfo.AvatarUrl,
}
return userInfo, nil
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -143,7 +144,7 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -178,7 +179,7 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -92,7 +93,7 @@ func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
rbs, err := io.ReadAll(resp.Body)
rbs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -15,11 +15,13 @@
package idp
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
"golang.org/x/oauth2"
@ -60,9 +62,38 @@ func (idp *GithubIdProvider) getConfig() *oauth2.Config {
return config
}
type GithubToken struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
Scope string `json:"scope"`
Error string `json:"error"`
}
func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
params := &struct {
Code string `json:"code"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
}{code, idp.Config.ClientID, idp.Config.ClientSecret}
data, err := idp.postWithBody(params, idp.Config.Endpoint.TokenURL)
if err != nil {
return nil, err
}
pToken := &GithubToken{}
if err = json.Unmarshal(data, pToken); err != nil {
return nil, err
}
if pToken.Error != "" {
return nil, fmt.Errorf("err: %s", pToken.Error)
}
token := &oauth2.Token{
AccessToken: pToken.AccessToken,
TokenType: "Bearer",
}
return token, nil
}
//{
@ -172,7 +203,7 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -192,3 +223,30 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
return &userInfo, nil
}
func (idp *GithubIdProvider) postWithBody(body interface{}, url string) ([]byte, error) {
bs, err := json.Marshal(body)
if err != nil {
return nil, err
}
r := strings.NewReader(string(bs))
req, _ := http.NewRequest("POST", url, r)
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
resp, err := idp.Client.Do(req)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
return data, nil
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -85,7 +85,7 @@ func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -209,7 +209,7 @@ func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -19,7 +19,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@ -95,7 +95,7 @@ func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -231,6 +231,10 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
value.Add("code", code)
}
accessToken, err := idp.Session.Authorize(idp.Provider, value)
if err != nil {
return nil, err
}
//Get ExpiresAt's value
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
if valueOfExpire.IsValid() {
@ -240,7 +244,8 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
AccessToken: accessToken,
Expiry: expireAt,
}
return &token, err
return &token, nil
}
func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@ -69,7 +69,7 @@ func (idp *InfoflowInternalIdProvider) GetToken(code string) (*oauth2.Token, err
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -147,7 +147,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -165,7 +165,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
return nil, err
}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -143,7 +144,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -161,7 +162,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -196,7 +197,7 @@ func (idp *InfoflowIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,6 +17,7 @@ package idp
import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -168,7 +169,7 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
resp, err := idp.Client.Do(req)
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
err = resp.Body.Close()
if err != nil {
return nil, err
@ -200,7 +201,7 @@ func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, e
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
@ -84,7 +85,7 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
rbs, err := io.ReadAll(resp.Body)
rbs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -322,7 +323,7 @@ func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([
}
}(resp.Body)
bs, err := io.ReadAll(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -70,6 +70,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Baidu" {
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Alipay" {
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Infoflow" {
if subType == "Internal" {
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
@ -78,6 +80,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
} else {
return nil
}
} else if typ == "Casdoor" {
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if isGothSupport(typ) {
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
}

View File

@ -18,7 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
@ -75,7 +75,10 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
defer resp.Body.Close()
tokenContent, err := io.ReadAll(resp.Body)
tokenContent, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
re := regexp.MustCompile("token=(.*?)&")
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
@ -145,7 +148,10 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
openIdBody, err := io.ReadAll(resp.Body)
openIdBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
@ -161,7 +167,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
userInfoBody, err := io.ReadAll(resp.Body)
userInfoBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -178,6 +184,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
userInfo := UserInfo{
Id: openId,
Username: qqUserInfo.Nickname,
DisplayName: qqUserInfo.Nickname,
AvatarUrl: qqUserInfo.FigureurlQq1,
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
@ -72,7 +72,7 @@ func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -111,6 +111,7 @@ type WecomInternalUserInfo struct {
Email string `json:"email"`
Avatar string `json:"avatar"`
OpenId string `json:"open_userid"`
UserId string `json:"userid"`
}
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
@ -122,7 +123,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -143,7 +144,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, err
}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -156,7 +157,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
}
userInfo := UserInfo{
Id: infoResp.OpenId,
Id: infoResp.UserId,
Username: infoResp.Name,
DisplayName: infoResp.Name,
Email: infoResp.Email,

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -194,7 +195,7 @@ func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte,
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -91,7 +92,7 @@ func (idp *WeiBoIdProvider) GetToken(code string) (*oauth2.Token, error) {
return
}
}(resp.Body)
bs, err := io.ReadAll(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -22,6 +22,7 @@ import (
"github.com/astaxie/beego/logs"
_ "github.com/astaxie/beego/session/redis"
"github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/routers"
@ -31,6 +32,7 @@ import (
func main() {
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
flag.Parse()
object.InitAdapter(*createDatabase)
object.InitDb()
object.InitDefaultStorageProvider()
@ -52,12 +54,12 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
if beego.AppConfig.String("redisEndpoint") == "" {
if conf.GetConfigString("redisEndpoint") == "" {
beego.BConfig.WebConfig.Session.SessionProvider = "file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
} else {
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
beego.BConfig.WebConfig.Session.SessionProviderConfig = beego.AppConfig.String("redisEndpoint")
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
}
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode

View File

@ -41,7 +41,7 @@ func InitConfig() {
func InitAdapter(createDatabase bool) {
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName"))
if createDatabase {
adapter.CreateDatabase()
}
@ -111,10 +111,10 @@ func (a *Adapter) close() {
}
func (a *Adapter) createTable() {
showSql, _ := beego.AppConfig.Bool("showSql")
showSql, _ := conf.GetConfigBool("showSql")
a.Engine.ShowSQL(showSql)
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
a.Engine.SetTableMapper(tbMapper)

View File

@ -229,7 +229,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
application.OrganizationObj.PasswordSalt = "***"
}
}
return application
return application
}
func GetMaskedApplications(applications []*Application, userId string) []*Application {

View File

@ -19,14 +19,14 @@ import (
"fmt"
"io"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/proxy"
)
var defaultStorageProvider *Provider = nil
func InitDefaultStorageProvider() {
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider")
defaultStorageProviderStr := conf.GetConfigString("defaultStorageProvider")
if defaultStorageProviderStr != "" {
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
}

View File

@ -180,16 +180,15 @@ func CheckUserPassword(organization string, username string, password string) (*
return nil, "the user is forbidden to sign in, please contact the administrator"
}
msg := CheckPassword(user, password)
if msg != "" {
//for ldap users
if user.Ldap != "" {
return checkLdapUserPassword(user, password)
if user.Ldap != "" {
//ONLY for ldap users
return checkLdapUserPassword(user, password)
} else {
msg := CheckPassword(user, password)
if msg != "" {
return nil, msg
}
return nil, msg
}
return user, ""
}

View File

@ -15,29 +15,25 @@
package object
import (
_ "embed"
"io/ioutil"
"github.com/casdoor/casdoor/util"
)
//go:embed token_jwt_key.pem
var tokenJwtPublicKey string
//go:embed token_jwt_key.key
var tokenJwtPrivateKey string
func InitDb() {
initBuiltInOrganization()
initBuiltInUser()
initBuiltInApplication()
initBuiltInCert()
initBuiltInLdap()
existed := initBuiltInOrganization()
if !existed {
initBuiltInUser()
initBuiltInApplication()
initBuiltInCert()
initBuiltInLdap()
}
}
func initBuiltInOrganization() {
func initBuiltInOrganization() bool {
organization := getOrganization("admin", "built-in")
if organization != nil {
return
return true
}
organization = &Organization{
@ -53,6 +49,7 @@ func initBuiltInOrganization() {
Tags: []string{},
}
AddOrganization(organization)
return false
}
func initBuiltInUser() {
@ -122,7 +119,22 @@ func initBuiltInApplication() {
AddApplication(application)
}
func readTokenFromFile() (string, string) {
pemPath := "./object/token_jwt_key.pem"
keyPath := "./object/token_jwt_key.key"
pem, err := ioutil.ReadFile(pemPath)
if err != nil {
return "", ""
}
key, err := ioutil.ReadFile(keyPath)
if err != nil {
return "", ""
}
return string(pem), string(key)
}
func initBuiltInCert() {
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile()
cert := getCert("admin", "cert-built-in")
if cert != nil {
return

View File

@ -20,7 +20,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"gopkg.in/square/go-jose.v2"
)
@ -58,7 +58,7 @@ func getOriginFromHost(host string) (string, string) {
func GetOidcDiscovery(host string) OidcDiscovery {
originFrontend, originBackend := getOriginFromHost(host)
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
if origin != "" {
originFrontend = origin
originBackend = origin

View File

@ -13,6 +13,7 @@
// limitations under the License.
//go:build !skipCi
// +build !skipCi
package object

View File

@ -33,7 +33,7 @@ type Provider struct {
SubType string `xorm:"varchar(100)" json:"subType"`
Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
ClientSecret string `xorm:"varchar(2000)" json:"clientSecret"`
ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
Cert string `xorm:"varchar(100)" json:"cert"`

View File

@ -34,6 +34,9 @@ func (application *Application) GetProviderItem(providerName string) *ProviderIt
}
func (pi *ProviderItem) IsProviderVisible() bool {
if pi.Provider == nil {
return false
}
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
}

View File

@ -18,8 +18,8 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
)
@ -27,7 +27,7 @@ var logPostOnly bool
func init() {
var err error
logPostOnly, err = beego.AppConfig.Bool("logPostOnly")
logPostOnly, err = conf.GetConfigBool("logPostOnly")
if err != nil {
//panic(err)
}

View File

@ -23,7 +23,7 @@ import (
"regexp"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig"
)
@ -73,7 +73,7 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{},
}
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
certEncodedData := ""
if samlResponse != "" {
certEncodedData = parseSamlResponse(samlResponse, provider.Type)

View File

@ -19,7 +19,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/storage"
"github.com/casdoor/casdoor/util"
)
@ -28,7 +28,7 @@ var isCloudIntranet bool
func init() {
var err error
isCloudIntranet, err = beego.AppConfig.Bool("isCloudIntranet")
isCloudIntranet, err = conf.GetConfigBool("isCloudIntranet")
if err != nil {
//panic(err)
}

View File

@ -10,6 +10,7 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !skipCi
// +build !skipCi

View File

@ -439,14 +439,15 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
TokenType: "Bearer",
}
AddToken(newToken)
DeleteToken(&token)
tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken,
IdToken: token.AccessToken,
RefreshToken: token.RefreshToken,
TokenType: token.TokenType,
ExpiresIn: token.ExpiresIn,
Scope: token.Scope,
AccessToken: newToken.AccessToken,
IdToken: newToken.AccessToken,
RefreshToken: newToken.RefreshToken,
TokenType: newToken.TokenType,
ExpiresIn: newToken.ExpiresIn,
Scope: newToken.Scope,
}
return tokenWrapper
@ -521,7 +522,8 @@ func GetPasswordToken(application *Application, username string, password string
if user == nil {
return nil, errors.New("error: the user does not exist")
}
if user.Password != password {
msg := CheckPassword(user, password)
if msg != "" {
return nil, errors.New("error: invalid username or password")
}
if user.IsForbidden {

View File

@ -15,11 +15,10 @@
package object
import (
_ "embed"
"fmt"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/golang-jwt/jwt/v4"
)
@ -67,7 +66,7 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
user.Password = ""
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
_, originBackend := getOriginFromHost(host)
if origin != "" {
originBackend = origin

View File

@ -18,7 +18,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -85,6 +85,8 @@ type User struct {
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
Apple string `xorm:"apple varchar(100)" json:"apple"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
@ -303,7 +305,7 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
"is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"}
}
if isGlobalAdmin {
columns = append(columns, "name")
columns = append(columns, "name", "email", "phone")
}
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
@ -428,7 +430,7 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
if user == nil {
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
}
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
_, originBackend := getOriginFromHost(host)
if origin != "" {
originBackend = origin

View File

@ -52,8 +52,8 @@ func UploadUsers(owner string, fileId string) bool {
oldUserMap := getUserMap(owner)
newUsers := []*User{}
for _, line := range table {
if parseLineItem(&line, 0) == "" {
for index, line := range table {
if index == 0 || parseLineItem(&line, 0) == "" {
continue
}
@ -67,38 +67,42 @@ func UploadUsers(owner string, fileId string) bool {
Password: parseLineItem(&line, 6),
PasswordSalt: parseLineItem(&line, 7),
DisplayName: parseLineItem(&line, 8),
Avatar: parseLineItem(&line, 9),
FirstName: parseLineItem(&line, 9),
LastName: parseLineItem(&line, 10),
Avatar: parseLineItem(&line, 11),
PermanentAvatar: "",
Email: parseLineItem(&line, 10),
Phone: parseLineItem(&line, 11),
Location: parseLineItem(&line, 12),
Address: []string{parseLineItem(&line, 13)},
Affiliation: parseLineItem(&line, 14),
Title: parseLineItem(&line, 15),
IdCardType: parseLineItem(&line, 16),
IdCard: parseLineItem(&line, 17),
Homepage: parseLineItem(&line, 18),
Bio: parseLineItem(&line, 19),
Tag: parseLineItem(&line, 20),
Region: parseLineItem(&line, 21),
Language: parseLineItem(&line, 22),
Gender: parseLineItem(&line, 23),
Birthday: parseLineItem(&line, 24),
Education: parseLineItem(&line, 25),
Score: parseLineItemInt(&line, 26),
Ranking: parseLineItemInt(&line, 27),
Email: parseLineItem(&line, 12),
Phone: parseLineItem(&line, 13),
Location: parseLineItem(&line, 14),
Address: []string{parseLineItem(&line, 15)},
Affiliation: parseLineItem(&line, 16),
Title: parseLineItem(&line, 17),
IdCardType: parseLineItem(&line, 18),
IdCard: parseLineItem(&line, 19),
Homepage: parseLineItem(&line, 20),
Bio: parseLineItem(&line, 21),
Tag: parseLineItem(&line, 22),
Region: parseLineItem(&line, 23),
Language: parseLineItem(&line, 24),
Gender: parseLineItem(&line, 25),
Birthday: parseLineItem(&line, 26),
Education: parseLineItem(&line, 27),
Score: parseLineItemInt(&line, 28),
Karma: parseLineItemInt(&line, 29),
Ranking: parseLineItemInt(&line, 30),
IsDefaultAvatar: false,
IsOnline: parseLineItemBool(&line, 28),
IsAdmin: parseLineItemBool(&line, 29),
IsGlobalAdmin: parseLineItemBool(&line, 30),
IsForbidden: parseLineItemBool(&line, 31),
IsDeleted: parseLineItemBool(&line, 32),
SignupApplication: parseLineItem(&line, 33),
IsOnline: parseLineItemBool(&line, 31),
IsAdmin: parseLineItemBool(&line, 32),
IsGlobalAdmin: parseLineItemBool(&line, 33),
IsForbidden: parseLineItemBool(&line, 34),
IsDeleted: parseLineItemBool(&line, 35),
SignupApplication: parseLineItem(&line, 36),
Hash: "",
PreHash: "",
CreatedIp: parseLineItem(&line, 34),
LastSigninTime: parseLineItem(&line, 35),
LastSigninIp: parseLineItem(&line, 36),
CreatedIp: parseLineItem(&line, 37),
LastSigninTime: parseLineItem(&line, 38),
LastSigninIp: parseLineItem(&line, 39),
Ldap: "",
Properties: map[string]string{},
}

View File

@ -20,7 +20,7 @@ import (
"math/rand"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -129,7 +129,7 @@ func CheckVerificationCode(dest, code string) string {
return "Code has not been sent yet!"
}
timeout, err := beego.AppConfig.Int64("verificationCodeTimeout")
timeout, err := conf.GetConfigInt64("verificationCodeTimeout")
if err != nil {
panic(err)
}

View File

@ -21,7 +21,7 @@ import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"golang.org/x/net/proxy"
)
@ -54,7 +54,7 @@ func isAddressOpen(address string) bool {
}
func getProxyHttpClient() *http.Client {
sock5Proxy := beego.AppConfig.String("sock5Proxy")
sock5Proxy := conf.GetConfigString("sock5Proxy")
if sock5Proxy == "" {
return &http.Client{}
}

View File

@ -478,6 +478,39 @@
}
}
},
"/api/buy-product": {
"post": {
"tags": [
"Product API"
],
"description": "buy product",
"operationId": "ApiController.BuyProduct",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id of the product",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "providerName",
"description": "The name of the provider",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/check-ldap-users-exist": {
"post": {
"tags": [
@ -1710,6 +1743,49 @@
}
}
},
"/api/get-user-payments": {
"get": {
"tags": [
"Payment API"
],
"description": "get payments for a user",
"operationId": "ApiController.GetUserPayments",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of payments",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "organization",
"description": "The organization of the user",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "user",
"description": "The username of the user",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Payment"
}
}
}
}
}
},
"/api/get-users": {
"get": {
"tags": [
@ -1936,6 +2012,36 @@
}
}
},
"/api/login/oauth/introspect": {
"post": {
"description": "The introspection endpoint is an OAuth 2.0 endpoint that takes a",
"operationId": "ApiController.IntrospectToken",
"parameters": [
{
"in": "formData",
"name": "token",
"description": "access_token's value or refresh_token's value",
"required": true,
"type": "string"
},
{
"in": "formData",
"name": "token_type_hint",
"description": "the token type access_token or refresh_token",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.IntrospectionResponse"
}
}
}
}
},
"/api/login/oauth/logout": {
"get": {
"tags": [
@ -2015,7 +2121,6 @@
"in": "query",
"name": "client_secret",
"description": "OAuth client secret",
"required": true,
"type": "string"
}
],
@ -2046,6 +2151,34 @@
}
}
},
"/api/notify-payment": {
"post": {
"tags": [
"Payment API"
],
"description": "notify payment",
"operationId": "ApiController.NotifyPayment",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the payment",
"required": true,
"schema": {
"$ref": "#/definitions/object.Payment"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/send-verification-code": {
"post": {
"tags": [
@ -2664,11 +2797,11 @@
}
},
"definitions": {
"2015.0xc0000edb90.false": {
"2026.0xc000380de0.false": {
"title": "false",
"type": "object"
},
"2049.0xc0000edbc0.false": {
"2060.0xc000380e10.false": {
"title": "false",
"type": "object"
},
@ -2685,10 +2818,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/2015.0xc0000edb90.false"
"$ref": "#/definitions/2026.0xc000380de0.false"
},
"data2": {
"$ref": "#/definitions/2049.0xc0000edbc0.false"
"$ref": "#/definitions/2060.0xc000380e10.false"
},
"msg": {
"type": "string"
@ -2709,10 +2842,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/2015.0xc0000edb90.false"
"$ref": "#/definitions/2026.0xc000380de0.false"
},
"data2": {
"$ref": "#/definitions/2049.0xc0000edbc0.false"
"$ref": "#/definitions/2060.0xc000380e10.false"
},
"msg": {
"type": "string"
@ -2864,6 +2997,12 @@
"title": "Cert",
"type": "object",
"properties": {
"authorityPublicKey": {
"type": "string"
},
"authorityRootPublicKey": {
"type": "string"
},
"bitSize": {
"type": "integer",
"format": "int64"
@ -2913,6 +3052,54 @@
}
}
},
"object.IntrospectionResponse": {
"title": "IntrospectionResponse",
"type": "object",
"properties": {
"active": {
"type": "boolean"
},
"aud": {
"type": "array",
"items": {
"type": "string"
}
},
"client_id": {
"type": "string"
},
"exp": {
"type": "integer",
"format": "int64"
},
"iat": {
"type": "integer",
"format": "int64"
},
"iss": {
"type": "string"
},
"jti": {
"type": "string"
},
"nbf": {
"type": "integer",
"format": "int64"
},
"scope": {
"type": "string"
},
"sub": {
"type": "string"
},
"token_type": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"object.Organization": {
"title": "Organization",
"type": "object",
@ -2950,6 +3137,12 @@
"phonePrefix": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"websiteUrl": {
"type": "string"
}
@ -2959,19 +3152,19 @@
"title": "Payment",
"type": "object",
"properties": {
"amount": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"currency": {
"type": "string"
},
"detail": {
"type": "string"
},
"displayName": {
"type": "string"
},
"good": {
"message": {
"type": "string"
},
"name": {
@ -2983,12 +3176,31 @@
"owner": {
"type": "string"
},
"payUrl": {
"type": "string"
},
"price": {
"type": "number",
"format": "double"
},
"productDisplayName": {
"type": "string"
},
"productName": {
"type": "string"
},
"provider": {
"type": "string"
},
"returnUrl": {
"type": "string"
},
"state": {
"type": "string"
},
"tag": {
"type": "string"
},
"type": {
"type": "string"
},
@ -3074,8 +3286,8 @@
"type": "string"
},
"price": {
"type": "integer",
"format": "int64"
"type": "number",
"format": "double"
},
"providers": {
"type": "array",
@ -3087,6 +3299,9 @@
"type": "integer",
"format": "int64"
},
"returnUrl": {
"type": "string"
},
"sold": {
"type": "integer",
"format": "int64"
@ -3112,6 +3327,9 @@
"category": {
"type": "string"
},
"cert": {
"type": "string"
},
"clientId": {
"type": "string"
},
@ -3482,6 +3700,9 @@
"birthday": {
"type": "string"
},
"casdoor": {
"type": "string"
},
"createdIp": {
"type": "string"
},

View File

@ -309,6 +309,28 @@ paths:
description: object
schema:
$ref: '#/definitions/Response'
/api/buy-product:
post:
tags:
- Product API
description: buy product
operationId: ApiController.BuyProduct
parameters:
- in: query
name: id
description: The id of the product
required: true
type: string
- in: query
name: providerName
description: The name of the provider
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/check-ldap-users-exist:
post:
tags:
@ -1111,6 +1133,35 @@ paths:
responses:
"200":
description: '{int} int The count of filtered users for an organization'
/api/get-user-payments:
get:
tags:
- Payment API
description: get payments for a user
operationId: ApiController.GetUserPayments
parameters:
- in: query
name: owner
description: The owner of payments
required: true
type: string
- in: query
name: organization
description: The organization of the user
required: true
type: string
- in: query
name: user
description: The username of the user
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Payment'
/api/get-users:
get:
tags:
@ -1262,6 +1313,26 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/object.TokenWrapper'
/api/login/oauth/introspect:
post:
description: The introspection endpoint is an OAuth 2.0 endpoint that takes a
operationId: ApiController.IntrospectToken
parameters:
- in: formData
name: token
description: access_token's value or refresh_token's value
required: true
type: string
- in: formData
name: token_type_hint
description: the token type access_token or refresh_token
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.IntrospectionResponse'
/api/login/oauth/logout:
get:
tags:
@ -1318,7 +1389,6 @@ paths:
- in: query
name: client_secret
description: OAuth client secret
required: true
type: string
responses:
"200":
@ -1336,6 +1406,24 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/notify-payment:
post:
tags:
- Payment API
description: notify payment
operationId: ApiController.NotifyPayment
parameters:
- in: body
name: body
description: The details of the payment
required: true
schema:
$ref: '#/definitions/object.Payment'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/send-verification-code:
post:
tags:
@ -1743,10 +1831,10 @@ paths:
schema:
$ref: '#/definitions/object.Userinfo'
definitions:
2015.0xc0000edb90.false:
2026.0xc000380de0.false:
title: "false"
type: object
2049.0xc0000edbc0.false:
2060.0xc000380e10.false:
title: "false"
type: object
RequestForm:
@ -1760,9 +1848,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/2015.0xc0000edb90.false'
$ref: '#/definitions/2026.0xc000380de0.false'
data2:
$ref: '#/definitions/2049.0xc0000edbc0.false'
$ref: '#/definitions/2060.0xc000380e10.false'
msg:
type: string
name:
@ -1776,9 +1864,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/2015.0xc0000edb90.false'
$ref: '#/definitions/2026.0xc000380de0.false'
data2:
$ref: '#/definitions/2049.0xc0000edbc0.false'
$ref: '#/definitions/2060.0xc000380e10.false'
msg:
type: string
name:
@ -1880,6 +1968,10 @@ definitions:
title: Cert
type: object
properties:
authorityPublicKey:
type: string
authorityRootPublicKey:
type: string
bitSize:
type: integer
format: int64
@ -1912,6 +2004,39 @@ definitions:
type: string
value:
type: string
object.IntrospectionResponse:
title: IntrospectionResponse
type: object
properties:
active:
type: boolean
aud:
type: array
items:
type: string
client_id:
type: string
exp:
type: integer
format: int64
iat:
type: integer
format: int64
iss:
type: string
jti:
type: string
nbf:
type: integer
format: int64
scope:
type: string
sub:
type: string
token_type:
type: string
username:
type: string
object.Organization:
title: Organization
type: object
@ -1938,21 +2063,25 @@ definitions:
type: string
phonePrefix:
type: string
tags:
type: array
items:
type: string
websiteUrl:
type: string
object.Payment:
title: Payment
type: object
properties:
amount:
type: string
createdTime:
type: string
currency:
type: string
detail:
type: string
displayName:
type: string
good:
message:
type: string
name:
type: string
@ -1960,10 +2089,23 @@ definitions:
type: string
owner:
type: string
payUrl:
type: string
price:
type: number
format: double
productDisplayName:
type: string
productName:
type: string
provider:
type: string
returnUrl:
type: string
state:
type: string
tag:
type: string
type:
type: string
user:
@ -2021,8 +2163,8 @@ definitions:
owner:
type: string
price:
type: integer
format: int64
type: number
format: double
providers:
type: array
items:
@ -2030,6 +2172,8 @@ definitions:
quantity:
type: integer
format: int64
returnUrl:
type: string
sold:
type: integer
format: int64
@ -2047,6 +2191,8 @@ definitions:
type: string
category:
type: string
cert:
type: string
clientId:
type: string
clientId2:
@ -2296,6 +2442,8 @@ definitions:
type: string
birthday:
type: string
casdoor:
type: string
createdIp:
type: string
createdTime:

View File

@ -39,4 +39,4 @@ func IsPhoneCnValid(phone string) bool {
func getMaskedPhone(phone string) string {
return rePhone.ReplaceAllString(phone, "$1****$2")
}
}

View File

@ -20,7 +20,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"os"
"io/ioutil"
"strconv"
"strings"
"time"
@ -162,7 +162,7 @@ func GetMinLenStr(strs ...string) string {
}
func ReadStringFromPath(path string) string {
data, err := os.ReadFile(path)
data, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
@ -171,7 +171,7 @@ func ReadStringFromPath(path string) string {
}
func WriteStringToPath(s string, path string) {
err := os.WriteFile(path, []byte(s), 0644)
err := ioutil.WriteFile(path, []byte(s), 0644)
if err != nil {
panic(err)
}
@ -220,7 +220,7 @@ func GetMaskedEmail(email string) string {
username := maskString(tokens[0])
domain := tokens[1]
domainTokens := strings.Split(domain, ".")
domainTokens[len(domainTokens) - 2] = maskString(domainTokens[len(domainTokens) - 2])
domainTokens[len(domainTokens)-2] = maskString(domainTokens[len(domainTokens)-2])
return fmt.Sprintf("%s@%s", username, strings.Join(domainTokens, "."))
}
@ -228,6 +228,6 @@ func maskString(str string) string {
if len(str) <= 2 {
return str
} else {
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str) - 2), str[len(str) - 1])
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str)-2), str[len(str)-1])
}
}
}

View File

@ -245,3 +245,4 @@ func TestSnakeString(t *testing.T) {
})
}
}

View File

@ -235,8 +235,12 @@ class App extends Component {
});
Setting.showMessage("success", `Logged out successfully`);
Setting.goToLinkSoft(this, "/");
let redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
}else{
Setting.goToLinkSoft(this, "/");
}
} else {
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
}
@ -553,22 +557,22 @@ class App extends Component {
</Link>
)
}
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: '64px', width: '100%', position: 'absolute'}}
>
<div>
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: '64px', width: '80%', position: 'absolute'}}
>
{
this.renderMenu()
}
</Menu>
{
this.renderMenu()
this.renderAccount()
}
<div style = {{float: 'right'}}>
{
this.renderAccount()
}
<SelectLanguageBox/>
</div>
</Menu>
<SelectLanguageBox/>
</div>
</Header>
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
<Card className="content-warp-card">

View File

@ -83,6 +83,10 @@ class ProductBuyPage extends React.Component {
}
}
getPrice(product) {
return `${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`;
}
getProviders(product) {
if (this.state.providers.length === 0 || product.providers.length === 0) {
return [];
@ -207,7 +211,9 @@ class ProductBuyPage extends React.Component {
</Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Price")}>
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
{`${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`}
{
this.getPrice(product)
}
</span>
</Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</span></Descriptions.Item>

View File

@ -303,7 +303,7 @@ class ProviderEditPage extends React.Component {
)
}
{
this.state.provider.type !== "Adfs" ? null : (
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" ? null : (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :

View File

@ -22,6 +22,7 @@ import copy from "copy-to-clipboard";
import {authConfig} from "./auth/Auth";
import {Helmet} from "react-helmet";
import moment from "moment";
import * as Conf from "./Conf";
export let ServerUrl = "";
@ -29,12 +30,17 @@ export let ServerUrl = "";
export const StaticBaseUrl = "https://cdn.casbin.org";
// https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html
export const CountryRegionData = getCountryRegionData()
export const CountryRegionData = getCountryRegionData();
export function getCountryRegionData() {
let language = i18next.language;
if (language === null || language === "null") {
language = Conf.DefaultLanguage;
}
var countries = require("i18n-iso-countries");
countries.registerLocale(require("i18n-iso-countries/langs/" + i18next.language + ".json"));
var data = countries.getNames(i18next.language, {select: "official"});
countries.registerLocale(require("i18n-iso-countries/langs/" + language + ".json"));
var data = countries.getNames(language, {select: "official"});
var result = []
for (var i in data)
result.push({code:i, name:data[i]})
@ -70,15 +76,7 @@ export function isProviderVisible(providerItem) {
return false;
}
if (providerItem.provider.type === "GitHub") {
if (isLocalhost()) {
return providerItem.provider.name.includes("localhost");
} else {
return !providerItem.provider.name.includes("localhost");
}
} else {
return true;
}
return true;
}
export function isProviderVisibleForSignUp(providerItem) {
@ -404,6 +402,8 @@ export function getProviderTypeOptions(category) {
{id: 'GitLab', name: 'GitLab'},
{id: 'Adfs', name: 'Adfs'},
{id: 'Baidu', name: 'Baidu'},
{id: 'Alipay', name: 'Alipay'},
{id: 'Casdoor', name: 'Casdoor'},
{id: 'Infoflow', name: 'Infoflow'},
{id: 'Apple', name: 'Apple'},
{id: 'AzureAD', name: 'AzureAD'},

View File

@ -42,7 +42,7 @@ class SyncerListPage extends BaseListPage {
affiliationTable: "",
avatarBaseUrl: "",
syncInterval: 10,
isEnabled: true,
isEnabled: false,
}
}

View File

@ -224,7 +224,11 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
</Col>
<Col style={{paddingRight: '20px'}} span={11} >
<Input value={this.state.user.email} disabled />
<Input value={this.state.user.email}
disabled={this.state.user.id === this.props.account?.id ? true : !Setting.isAdminUser(this.props.account)}
onChange={e => {
this.updateUserField('email', e.target.value);
}} />
</Col>
<Col span={11} >
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
@ -235,7 +239,11 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
</Col>
<Col style={{paddingRight: '20px'}} span={11} >
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`} disabled />
<Input value={this.state.user.phone} addonBefore={`+${this.state.application?.organizationObj.phonePrefix}`}
disabled={this.state.user.id === this.props.account?.id ? true : !Setting.isAdminUser(this.props.account)}
onChange={e => {
this.updateUserField('phone', e.target.value);
}}/>
</Col>
<Col span={11} >
{ this.state.user.id === this.props.account?.id ? (<ResetModal org={this.state.application?.organizationObj} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}

View File

@ -0,0 +1,32 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({ width = 24, height = 24, color }) {
return <img src={`${StaticBaseUrl}/buttons/alipay.svg`} alt="Sign in with Alipay" style={{width: 24, height: 24}} />;
}
const config = {
text: "Sign in with Alipay",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"},
};
const AlipayLoginButton = createButton(config);
export default AlipayLoginButton;

View File

@ -106,6 +106,7 @@ class AuthCallback extends React.Component {
method: method,
};
const oAuthParams = Util.getOAuthGetParameters(innerParams);
const concatChar = oAuthParams?.redirectUri?.includes('?') ? '&' : '?';
AuthBackend.login(body, oAuthParams)
.then((res) => {
if (res.status === 'ok') {
@ -118,11 +119,11 @@ class AuthCallback extends React.Component {
Setting.goToLink(link);
} else if (responseType === "code") {
const code = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
// Util.showMessage("success", `Authorization code: ${res.data}`);
} else if (responseType === "token" || responseType === "id_token"){
const token = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}?${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
} else if (responseType === "link") {
const from = innerParams.get("from");
Setting.goToLinkSoft(this, from);

View File

@ -0,0 +1,32 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({ width = 24, height = 24, color }) {
return <img src={`${StaticBaseUrl}/buttons/casdoor.svg`} alt="Sign in with Casdoor" style={{width: 24, height: 24}} />;
}
const config = {
text: "Sign in with Casdoor",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"},
};
const CasdoorLoginButton = createButton(config);
export default CasdoorLoginButton;

View File

@ -36,6 +36,8 @@ import LarkLoginButton from "./LarkLoginButton";
import GitLabLoginButton from "./GitLabLoginButton";
import AdfsLoginButton from "./AdfsLoginButton";
import BaiduLoginButton from "./BaiduLoginButton";
import AlipayLoginButton from "./AlipayLoginButton";
import CasdoorLoginButton from "./CasdoorLoginButton";
import InfoflowLoginButton from "./InfoflowLoginButton";
import AppleLoginButton from "./AppleLoginButton"
import AzureADLoginButton from "./AzureADLoginButton";
@ -56,7 +58,9 @@ class LoginPage extends React.Component {
isCodeSignin: false,
msg: null,
username: null,
validEmailOrPhone: false
validEmailOrPhone: false,
validEmail: false,
validPhone: false,
};
}
@ -135,6 +139,7 @@ class LoginPage extends React.Component {
Setting.goToLink(link);
} else if (responseType === "code") {
const code = res.data;
const concatChar = oAuthParams?.redirectUri?.includes('?') ? '&' : '?';
if (Setting.hasPromptPage(application)) {
AuthBackend.getAccount("")
@ -147,7 +152,7 @@ class LoginPage extends React.Component {
this.onUpdateAccount(account);
if (Setting.isPromptAnswered(account, application)) {
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
} else {
Setting.goToLinkSoft(ths, `/prompt/${application.name}?redirectUri=${oAuthParams.redirectUri}&code=${code}&state=${oAuthParams.state}`);
}
@ -156,7 +161,7 @@ class LoginPage extends React.Component {
}
});
} else {
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
}
// Util.showMessage("success", `Authorization code: ${res.data}`);
@ -198,8 +203,12 @@ class LoginPage extends React.Component {
return <GitLabLoginButton text={text} align={"center"} />
} else if (type === "Adfs") {
return <AdfsLoginButton text={text} align={"center"} />
} else if (type === "Casdoor") {
return <CasdoorLoginButton text={text} align={"center"} />
} else if (type === "Baidu") {
return <BaiduLoginButton text={text} align={"center"} />
} else if (type === "Alipay") {
return <AlipayLoginButton text={text} align={"center"} />
} else if (type === "Infoflow") {
return <InfoflowLoginButton text={text} align={"center"} />
} else if (type === "Apple") {
@ -344,6 +353,12 @@ class LoginPage extends React.Component {
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
}
}
if (Setting.isValidPhone(this.state.username)) {
this.setState({validPhone: true})
}
if (Setting.isValidEmail(this.state.username)) {
this.setState({validEmail: true})
}
this.setState({validEmailOrPhone: true});
return Promise.resolve();
}
@ -369,7 +384,7 @@ class LoginPage extends React.Component {
>
<CountDownInput
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
onButtonClickArgs={[this.state.username, "", Setting.getApplicationOrgName(application), true]}
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationOrgName(application)]}
/>
</Form.Item>
) : (

View File

@ -147,7 +147,10 @@ class PromptPage extends React.Component {
if (res.status === 'ok') {
this.onUpdateAccount(null);
const redirectUrl = this.getRedirectUrl();
let redirectUrl = this.getRedirectUrl();
if (redirectUrl === "") {
redirectUrl = res.data2
}
if (redirectUrl !== "") {
Setting.goToLink(redirectUrl);
} else {

View File

@ -15,7 +15,7 @@
import React from "react";
import {Tooltip} from "antd";
import * as Util from "./Util";
import {StaticBaseUrl} from "../Setting";
import * as Setting from "../Setting";
const authInfo = {
Google: {
@ -78,6 +78,14 @@ const authInfo = {
scope: "basic",
endpoint: "http://openapi.baidu.com/oauth/2.0/authorize",
},
Alipay: {
scope: "basic",
endpoint: "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm",
},
Casdoor: {
scope: "openid%20profile%20email",
endpoint: "http://example.com",
},
Infoflow: {
endpoint: "https://xpc.im.baidu.com/oauth2/authorize",
},
@ -101,71 +109,71 @@ const authInfo = {
const otherProviderInfo = {
SMS: {
"Aliyun SMS": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
logo: `${Setting.StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/sms",
},
"Tencent Cloud SMS": {
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
logo: `${Setting.StaticBaseUrl}/img/social_tencent_cloud.jpg`,
url: "https://cloud.tencent.com/product/sms",
},
"Volc Engine SMS": {
logo: `${StaticBaseUrl}/img/social_volc_engine.jpg`,
logo: `${Setting.StaticBaseUrl}/img/social_volc_engine.jpg`,
url: "https://www.volcengine.com/products/cloud-sms",
},
"Huawei Cloud SMS": {
logo: `${StaticBaseUrl}/img/social_huawei.png`,
logo: `${Setting.StaticBaseUrl}/img/social_huawei.png`,
url: "https://www.huaweicloud.com/product/msgsms.html",
},
},
Email: {
"Default": {
logo: `${StaticBaseUrl}/img/social_default.png`,
logo: `${Setting.StaticBaseUrl}/img/social_default.png`,
url: "",
},
},
Storage: {
"Local File System": {
logo: `${StaticBaseUrl}/img/social_file.png`,
logo: `${Setting.StaticBaseUrl}/img/social_file.png`,
url: "",
},
"AWS S3": {
logo: `${StaticBaseUrl}/img/social_aws.png`,
logo: `${Setting.StaticBaseUrl}/img/social_aws.png`,
url: "https://aws.amazon.com/s3",
},
"Aliyun OSS": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
logo: `${Setting.StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/oss",
},
"Tencent Cloud COS": {
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
logo: `${Setting.StaticBaseUrl}/img/social_tencent_cloud.jpg`,
url: "https://cloud.tencent.com/product/cos",
},
},
SAML: {
"Aliyun IDaaS": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
logo: `${Setting.StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/idaas"
},
"Keycloak": {
logo: `${StaticBaseUrl}/img/social_keycloak.png`,
logo: `${Setting.StaticBaseUrl}/img/social_keycloak.png`,
url: "https://www.keycloak.org/"
},
},
Payment: {
"Alipay": {
logo: `${StaticBaseUrl}/img/payment_alipay.png`,
logo: `${Setting.StaticBaseUrl}/img/payment_alipay.png`,
url: "https://www.alipay.com/"
},
"WeChat Pay": {
logo: `${StaticBaseUrl}/img/payment_wechat_pay.png`,
logo: `${Setting.StaticBaseUrl}/img/payment_wechat_pay.png`,
url: "https://pay.weixin.qq.com/"
},
"PayPal": {
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
logo: `${Setting.StaticBaseUrl}/img/payment_paypal.png`,
url: "https://www.paypal.com/"
},
"GC": {
logo: `${StaticBaseUrl}/img/payment_gc.png`,
logo: `${Setting.StaticBaseUrl}/img/payment_gc.png`,
url: "https://gc.org"
},
},
@ -173,7 +181,7 @@ const otherProviderInfo = {
export function getProviderLogo(provider) {
if (provider.category === "OAuth") {
return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
return `${Setting.StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
} else {
return otherProviderInfo[provider.category][provider.type].logo;
}
@ -283,6 +291,10 @@ export function getAuthUrl(application, provider, method) {
return `${provider.domain}/adfs/oauth2/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&nonce=casdoor&scope=openid`;
} else if (provider.type === "Baidu") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&display=popup`;
} else if (provider.type === "Alipay") {
return `${endpoint}?app_id=${provider.clientId}&scope=auth_user&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&display=popup`;
} else if (provider.type === "Casdoor") {
return `${provider.domain}/login/oauth/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Infoflow"){
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}?state=${state}`
} else if (provider.type === "Apple") {

View File

@ -20,8 +20,8 @@
"Password ON": "开启密码",
"Password ON - Tooltip": "是否允许密码登录",
"Please select a HTML file": "请选择一个HTML文件",
"Redirect URL": "回调URL",
"Redirect URLs": "回调URLs",
"Redirect URL": "重定向 URL",
"Redirect URLs": "重定向 URLs",
"Redirect URLs - Tooltip": "登录成功后重定向地址列表",
"Refresh token expire": "Refresh Token过期时间",
"Refresh token expire - Tooltip": "Refresh Token过期时间",
@ -304,7 +304,7 @@
"Pay": "支付方式",
"Payment providers": "支付提供商",
"Payment providers - Tooltip": "支付提供商 - 工具提示",
"Paypal": "Paypal",
"Paypal": "PayPal(贝宝)",
"Placing order...": "正在下单...",
"Price": "价格",
"Price - Tooltip": "价格 - 工具提示",

BIN
xlsx/user_test.xlsx Normal file

Binary file not shown.