Compare commits

..

40 Commits

Author SHA1 Message Date
0b546bba5e fix: grantTypes undefined err (#654)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-08 21:54:48 +08:00
938cdbccf4 fix: link type error (#653)
* fix: signin button error in signup page

* fix: type error
2022-04-08 20:01:30 +08:00
801302c6e7 feat: support user migration from Keycloak using syncer (#645)
* feat: support user migration from Keycloak using syncer

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* feat: add more Keycloak columns

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: requested changes

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-04-06 20:38:14 +08:00
91602d2b21 Enable extra pages. 2022-04-06 20:36:31 +08:00
86b3a078ef fix: sign In button in the result page has broken (#646)
* fix: sign In button in the result page has broken

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

* fix: code format

Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-05 08:49:11 +08:00
abc15b88c8 fix: change goth version (#644)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-04-04 15:58:51 +08:00
3cf1b990be feat: support CAS with organizations and applications (#621) 2022-04-04 00:09:04 +08:00
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
9f7924a6e0 fix: mask email and phone number on the backend (#563)
* fix: mask email and phone number on the backend

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: login with masked email or phone

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: improve regex

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-15 12:54:57 +08:00
377e200837 fix: repair the problem that AutoSigninFilter middleware doesn't recognize the access_token request parameter (#569)
AutoSigninFilter method only checks for `accessToken` request parameters or `Authorization` request header, doesn't recognize `access_token` request parameters, now added, use `utils.GetMaxLenStr()` method to get the maximum length characters
2022-03-15 12:52:44 +08:00
93a76de044 fix: fix compile error in low go version (#568) 2022-03-15 12:49:12 +08:00
97 changed files with 2423 additions and 344 deletions

View File

@ -15,7 +15,6 @@
package authz package authz
import ( import (
"github.com/astaxie/beego"
"github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model" "github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2" xormadapter "github.com/casbin/xorm-adapter/v2"
@ -28,8 +27,8 @@ var Enforcer *casbin.Enforcer
func InitAuthz() { func InitAuthz() {
var err error var err error
tableNamePrefix := beego.AppConfig.String("tableNamePrefix") tableNamePrefix := conf.GetConfigString("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true) a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -102,6 +101,7 @@ p, *, *, GET, /.well-known/openid-configuration, *, *
p, *, *, *, /.well-known/jwks, *, * p, *, *, *, /.well-known/jwks, *, *
p, *, *, GET, /api/get-saml-login, *, * p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, * p, *, *, POST, /api/acs, *, *
p, *, *, *, /cas, *, *
` `
sa := stringadapter.NewAdapter(ruleText) sa := stringadapter.NewAdapter(ruleText)

View File

@ -15,14 +15,49 @@
package conf package conf
import ( import (
"fmt"
"os" "os"
"strconv"
"strings" "strings"
"github.com/astaxie/beego" "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 { func GetBeegoConfDataSourceName() string {
dataSourceName := beego.AppConfig.String("dataSourceName") dataSourceName := GetConfigString("dataSourceName")
runningInDocker := os.Getenv("RUNNING_IN_DOCKER") runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
if runningInDocker == "true" { 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

@ -29,6 +29,7 @@ const (
ResponseTypeCode = "code" ResponseTypeCode = "code"
ResponseTypeToken = "token" ResponseTypeToken = "token"
ResponseTypeIdToken = "id_token" ResponseTypeIdToken = "id_token"
ResponseTypeCas = "cas"
) )
type RequestForm struct { type RequestForm struct {
@ -225,10 +226,15 @@ func (c *ApiController) Logout() {
user := c.GetSessionUsername() user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user) util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
c.SetSessionUsername("") c.SetSessionUsername("")
c.SetSessionData(nil) 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 // GetAccount

View File

@ -23,7 +23,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/idp" "github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
@ -83,8 +83,25 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
resp = tokenToResponse(token) resp = tokenToResponse(token)
} }
} else if form.Type == ResponseTypeCas {
//not oauth but CAS SSO protocol
service := c.Input().Get("service")
resp = wrapErrorResponse(nil)
if service != "" {
st, err := object.GenerateCasToken(userId, service)
if err != nil {
resp = wrapErrorResponse(err)
} else {
resp.Data = st
}
}
if application.EnableSigninSession || application.HasPromptPage() {
// The prompt page needs the user to be signed in
c.SetSessionUsername(userId)
}
} else { } else {
resp = &Response{Status: "error", Msg: fmt.Sprintf("Unknown response type: %s", form.Type)} resp = wrapErrorResponse(fmt.Errorf("Unknown response type: %s", form.Type))
} }
// if user did not check auto signin // if user did not check auto signin
@ -167,9 +184,16 @@ func (c *ApiController) Login() {
var verificationCodeType string var verificationCodeType string
var checkResult string var checkResult string
if form.Name != "" {
user = object.GetUserByFields(form.Organization, form.Name)
}
// check result through Email or Phone // check result through Email or Phone
if strings.Contains(form.Username, "@") { if strings.Contains(form.Username, "@") {
verificationCodeType = "email" verificationCodeType = "email"
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
form.Username = user.Email
}
checkResult = object.CheckVerificationCode(form.Username, form.Code) checkResult = object.CheckVerificationCode(form.Username, form.Code)
} else { } else {
verificationCodeType = "phone" verificationCodeType = "phone"
@ -178,6 +202,9 @@ func (c *ApiController) Login() {
c.ResponseError(responseText) c.ResponseError(responseText)
return return
} }
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
form.Username = user.Phone
}
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username) checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
checkResult = object.CheckVerificationCode(checkPhone, form.Code) checkResult = object.CheckVerificationCode(checkPhone, form.Code)
} }
@ -257,8 +284,8 @@ func (c *ApiController) Login() {
setHttpClient(idProvider, provider.Type) setHttpClient(idProvider, provider.Type)
if form.State != beego.AppConfig.String("authState") && form.State != application.Name { if form.State != conf.GetConfigString("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State)) c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", conf.GetConfigString("authState"), form.State))
return return
} }

View File

@ -72,6 +72,15 @@ func (c *ApiController) GetSessionUsername() string {
return user.(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) { func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData() sessionData := c.GetSessionData()
if sessionData != nil && if sessionData != nil &&

204
controllers/cas.go Normal file
View File

@ -0,0 +1,204 @@
// 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
import (
"fmt"
"net/http"
"net/url"
"strings"
"github.com/casdoor/casdoor/object"
)
const (
InvalidRequest string = "INVALID_REQUEST"
InvalidTicketSpec string = "INVALID_TICKET_SPEC"
UnauthorizedServiceProxy string = "UNAUTHORIZED_SERVICE_PROXY"
InvalidProxyCallback string = "INVALID_PROXY_CALLBACK"
InvalidTicket string = "INVALID_TICKET"
InvalidService string = "INVALID_SERVICE"
InteralError string = "INTERNAL_ERROR"
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
)
func (c *RootController) CasValidate() {
ticket := c.Input().Get("ticket")
service := c.Input().Get("service")
c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
if service == "" || ticket == "" {
c.Ctx.Output.Body([]byte("no\n"))
return
}
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
//check whether service is the one for which we previously issued token
if issuedService == service {
c.Ctx.Output.Body([]byte(fmt.Sprintf("yes\n%s\n", response.User)))
return
}
}
//token not found
c.Ctx.Output.Body([]byte("no\n"))
}
func (c *RootController) CasServiceAndProxyValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
service := c.Input().Get("service")
pgtUrl := c.Input().Get("pgtUrl")
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
}
//check whether all required parameters are met
if service == "" || ticket == "" {
c.sendCasAuthenticationResponseErr(InvalidRequest, "service and ticket must exist", format)
return
}
//find the token
if ok, response, issuedService := object.GetCasTokenByTicket(ticket); ok {
//check whether service is the one for which we previously issued token
if strings.HasPrefix(service, issuedService) {
serviceResponse.Success = response
} else {
//service not match
c.sendCasAuthenticationResponseErr(InvalidService, fmt.Sprintf("service %s and %s does not match", service, issuedService), format)
return
}
} else {
//token not found
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
return
}
if pgtUrl != "" && serviceResponse.Failure == nil {
//that means we are in proxy web flow
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service)
pgtiou := serviceResponse.Success.ProxyGrantingTicket
//todo: check whether it is https
pgtUrlObj, err := url.Parse(pgtUrl)
if pgtUrlObj.Scheme != "https" {
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
return
}
//make a request to pgturl passing pgt and pgtiou
if err != nil {
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
return
}
param := pgtUrlObj.Query()
param.Add("pgtId", pgt)
param.Add("pgtIou", pgtiou)
pgtUrlObj.RawQuery = param.Encode()
request, err := http.NewRequest("GET", pgtUrlObj.String(), nil)
if err != nil {
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
return
}
resp, err := http.DefaultClient.Do(request)
if err != nil || !(resp.StatusCode >= 200 && resp.StatusCode < 400) {
//failed to send request
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
return
}
}
// everything is ok, send the response
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) CasProxy() {
pgt := c.Input().Get("pgt")
targetService := c.Input().Get("targetService")
format := c.Input().Get("format")
if pgt == "" || targetService == "" {
c.sendCasProxyResponseErr(InvalidRequest, "pgt and targetService must exist", format)
return
}
ok, authenticationSuccess, issuedService := object.GetCasTokenByPgt(pgt)
if !ok {
c.sendCasProxyResponseErr(UnauthorizedService, "service not authorized", format)
return
}
newAuthenticationSuccess := authenticationSuccess.DeepCopy()
if newAuthenticationSuccess.Proxies == nil {
newAuthenticationSuccess.Proxies = &object.CasProxies{}
}
newAuthenticationSuccess.Proxies.Proxies = append(newAuthenticationSuccess.Proxies.Proxies, issuedService)
proxyTicket := object.StoreCasTokenForProxyTicket(&newAuthenticationSuccess, targetService)
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
ProxySuccess: &object.CasProxySuccess{
ProxyTicket: proxyTicket,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) sendCasProxyResponseErr(code, msg, format string) {
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
ProxyFailure: &object.CasProxyFailure{
Code: code,
Message: msg,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format string) {
serviceResponse := object.CasServiceResponse{
Xmlns: "http://www.yale.edu/tp/cas",
Failure: &object.CasAuthenticationFailure{
Code: code,
Message: msg,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()
} else {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}

View File

@ -178,7 +178,7 @@ func (c *ApiController) UpdateLdap() {
} }
if ldap.AutoSync != 0 { if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id) 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) object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
} }

View File

@ -179,6 +179,20 @@ func (c *ApiController) GetOAuthToken() {
if clientId == "" && clientSecret == "" { if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth() 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 host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, 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") clientSecret := c.Input().Get("client_secret")
host := c.Ctx.Request.Host 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.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
c.ServeJSON() 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

@ -194,15 +194,19 @@ func (c *ApiController) GetEmailAndPhone() {
return return
} }
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name} respUser := object.User{Name: user.Name}
var contentType string var contentType string
switch form.Username { switch form.Username {
case user.Email: case user.Email:
contentType = "email" contentType = "email"
respUser.Email = user.Email
case user.Phone: case user.Phone:
contentType = "phone" contentType = "phone"
respUser.Phone = user.Phone
case user.Name: case user.Name:
contentType = "username" contentType = "username"
respUser.Email = util.GetMaskedEmail(user.Email)
respUser.Phone = util.GetMaskedPhone(user.Phone)
} }
c.ResponseOk(respUser, contentType) c.ResponseOk(respUser, contentType)

View File

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

View File

@ -74,8 +74,16 @@ func (c *ApiController) SendVerificationCode() {
} }
sendResp := errors.New("Invalid dest type") sendResp := errors.New("Invalid dest type")
if user == nil && checkUser != "" && checkUser != "true" {
_, name := util.GetOwnerAndNameFromId(orgId)
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
}
switch destType { switch destType {
case "email": case "email":
if user != nil && util.GetMaskedEmail(user.Email) == dest {
dest = user.Email
}
if !util.IsEmailValid(dest) { if !util.IsEmailValid(dest) {
c.ResponseError("Invalid Email address") c.ResponseError("Invalid Email address")
return return
@ -84,6 +92,9 @@ func (c *ApiController) SendVerificationCode() {
provider := application.GetEmailProvider() provider := application.GetEmailProvider()
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest) sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone": case "phone":
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
dest = user.Phone
}
if !util.IsPhoneCnValid(dest) { if !util.IsPhoneCnValid(dest) {
c.ResponseError("Invalid phone number") c.ResponseError("Invalid phone number")
return return

View File

@ -28,6 +28,8 @@ func GetCredManager(passwordType string) CredManager {
return NewMd5UserSaltCredManager() return NewMd5UserSaltCredManager()
} else if passwordType == "bcrypt" { } else if passwordType == "bcrypt" {
return NewBcryptCredManager() return NewBcryptCredManager()
} else if passwordType == "pbkdf2-salt" {
return NewPbkdf2SaltCredManager()
} }
return nil return nil
} }

39
cred/pbkdf2-salt.go Normal file
View File

@ -0,0 +1,39 @@
// 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 cred
import (
"crypto/sha256"
"encoding/base64"
"golang.org/x/crypto/pbkdf2"
)
type Pbkdf2SaltCredManager struct{}
func NewPbkdf2SaltCredManager() *Pbkdf2SaltCredManager {
cm := &Pbkdf2SaltCredManager{}
return cm
}
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
// https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised
decodedSalt, _ := base64.StdEncoding.DecodeString(userSalt)
res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New)
return base64.StdEncoding.EncodeToString(res)
}
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
}

View File

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

5
go.mod
View File

@ -10,22 +10,21 @@ require (
github.com/casbin/casbin/v2 v2.30.1 github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.5.1 github.com/casbin/xorm-adapter/v2 v2.5.1
github.com/casdoor/go-sms-sender v0.2.0 github.com/casdoor/go-sms-sender v0.2.0
github.com/casdoor/goth v1.69.0-FIX1
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0 github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-pay/gopay v1.5.72 github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.5.0 github.com/go-sql-driver/mysql v1.5.0
github.com/golang-jwt/jwt/v4 v4.1.0 github.com/golang-jwt/jwt/v4 v4.2.0
github.com/google/uuid v1.2.0 github.com/google/uuid v1.2.0
github.com/jinzhu/configor v1.2.1 // indirect github.com/jinzhu/configor v1.2.1 // indirect
github.com/lestrrat-go/jwx v0.9.0 github.com/lestrrat-go/jwx v0.9.0
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8
github.com/qiangmzsx/string-adapter/v2 v2.1.0 github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76 github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.6.0 github.com/russellhaering/gosaml2 v0.6.0
github.com/russellhaering/goxmldsig v1.1.1 github.com/russellhaering/goxmldsig v1.1.1
github.com/satori/go.uuid v1.2.0 // indirect
github.com/smartystreets/goconvey v1.6.4 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/tealeg/xlsx v1.0.5 github.com/tealeg/xlsx v1.0.5

12
go.sum
View File

@ -79,10 +79,10 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM= github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0BIta0HGM=
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54= github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg= github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk= github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/casdoor/goth v1.69.0-FIX1 h1:24Y3tfaJxWGJbxickGe3F9y2c8X1PgsQynhxGXV1f9Q=
github.com/casdoor/goth v1.69.0-FIX1/go.mod h1:Om55nRo8CkeDkPSNBbzXW4G5uI28ZUkSk5S69dPek3s=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -135,10 +135,8 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -258,8 +256,6 @@ github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0= github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA= github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8 h1:JibQrkJapVsb0pweJ5T14jZuuYZZTjll0PZBw4XfSCI=
github.com/markbates/goth v1.68.1-0.20211006204042-9dc8905b41c8/go.mod h1:V2VcDMzDiMHW+YmqYl7i0cMiAUeCkAe4QE6jRKBhXZw=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To= github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911/go.mod h1:qccnGMcpgwcNaBnxqpJpWWUiPNr5H3O8eDgGV9gT5To=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=

View File

@ -19,7 +19,7 @@ import (
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"time" "time"
@ -88,7 +88,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err 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" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io/ioutil"
"net/http" "net/http"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -97,7 +97,7 @@ func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err 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" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -143,7 +144,7 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -178,7 +179,7 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil { if err != nil {
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

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

View File

@ -15,11 +15,13 @@
package idp package idp
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"time" "time"
"golang.org/x/oauth2" "golang.org/x/oauth2"
@ -60,9 +62,38 @@ func (idp *GithubIdProvider) getConfig() *oauth2.Config {
return 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) { func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client) params := &struct {
return idp.Config.Exchange(ctx, code) 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() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -192,3 +223,30 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
} }
return &userInfo, nil 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 ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strconv" "strconv"
@ -85,7 +85,7 @@ func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -209,7 +209,7 @@ func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err return nil, err
} }
data, err := io.ReadAll(resp.Body) data, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

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

View File

@ -22,35 +22,35 @@ import (
"time" "time"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/markbates/goth" "github.com/casdoor/goth"
"github.com/markbates/goth/providers/amazon" "github.com/casdoor/goth/providers/amazon"
"github.com/markbates/goth/providers/apple" "github.com/casdoor/goth/providers/apple"
"github.com/markbates/goth/providers/azuread" "github.com/casdoor/goth/providers/azuread"
"github.com/markbates/goth/providers/bitbucket" "github.com/casdoor/goth/providers/bitbucket"
"github.com/markbates/goth/providers/digitalocean" "github.com/casdoor/goth/providers/digitalocean"
"github.com/markbates/goth/providers/discord" "github.com/casdoor/goth/providers/discord"
"github.com/markbates/goth/providers/dropbox" "github.com/casdoor/goth/providers/dropbox"
"github.com/markbates/goth/providers/facebook" "github.com/casdoor/goth/providers/facebook"
"github.com/markbates/goth/providers/gitea" "github.com/casdoor/goth/providers/gitea"
"github.com/markbates/goth/providers/github" "github.com/casdoor/goth/providers/github"
"github.com/markbates/goth/providers/gitlab" "github.com/casdoor/goth/providers/gitlab"
"github.com/markbates/goth/providers/google" "github.com/casdoor/goth/providers/google"
"github.com/markbates/goth/providers/heroku" "github.com/casdoor/goth/providers/heroku"
"github.com/markbates/goth/providers/instagram" "github.com/casdoor/goth/providers/instagram"
"github.com/markbates/goth/providers/kakao" "github.com/casdoor/goth/providers/kakao"
"github.com/markbates/goth/providers/line" "github.com/casdoor/goth/providers/line"
"github.com/markbates/goth/providers/linkedin" "github.com/casdoor/goth/providers/linkedin"
"github.com/markbates/goth/providers/microsoftonline" "github.com/casdoor/goth/providers/microsoftonline"
"github.com/markbates/goth/providers/paypal" "github.com/casdoor/goth/providers/paypal"
"github.com/markbates/goth/providers/salesforce" "github.com/casdoor/goth/providers/salesforce"
"github.com/markbates/goth/providers/shopify" "github.com/casdoor/goth/providers/shopify"
"github.com/markbates/goth/providers/slack" "github.com/casdoor/goth/providers/slack"
"github.com/markbates/goth/providers/steam" "github.com/casdoor/goth/providers/steam"
"github.com/markbates/goth/providers/tumblr" "github.com/casdoor/goth/providers/tumblr"
"github.com/markbates/goth/providers/twitter" "github.com/casdoor/goth/providers/twitter"
"github.com/markbates/goth/providers/yahoo" "github.com/casdoor/goth/providers/yahoo"
"github.com/markbates/goth/providers/yandex" "github.com/casdoor/goth/providers/yandex"
"github.com/markbates/goth/providers/zoom" "github.com/casdoor/goth/providers/zoom"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -231,6 +231,10 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
value.Add("code", code) value.Add("code", code)
} }
accessToken, err := idp.Session.Authorize(idp.Provider, value) accessToken, err := idp.Session.Authorize(idp.Provider, value)
if err != nil {
return nil, err
}
//Get ExpiresAt's value //Get ExpiresAt's value
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt") valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
if valueOfExpire.IsValid() { if valueOfExpire.IsValid() {
@ -240,7 +244,8 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
AccessToken: accessToken, AccessToken: accessToken,
Expiry: expireAt, Expiry: expireAt,
} }
return &token, err
return &token, nil
} }
func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) { func (idp *GothIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {

View File

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

View File

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

View File

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

View File

@ -18,6 +18,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -84,7 +85,7 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
rbs, err := io.ReadAll(resp.Body) rbs, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -322,7 +323,7 @@ func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([
} }
}(resp.Body) }(resp.Body)
bs, err := io.ReadAll(resp.Body) bs, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err 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) return NewAdfsIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if typ == "Baidu" { } else if typ == "Baidu" {
return NewBaiduIdProvider(clientId, clientSecret, redirectUrl) return NewBaiduIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Alipay" {
return NewAlipayIdProvider(clientId, clientSecret, redirectUrl)
} else if typ == "Infoflow" { } else if typ == "Infoflow" {
if subType == "Internal" { if subType == "Internal" {
return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl) return NewInfoflowInternalIdProvider(clientId, clientSecret, appId, redirectUrl)
@ -78,6 +80,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
} else { } else {
return nil return nil
} }
} else if typ == "Casdoor" {
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if isGothSupport(typ) { } else if isGothSupport(typ) {
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl) return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,14 +19,14 @@ import (
"fmt" "fmt"
"io" "io"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
) )
var defaultStorageProvider *Provider = nil var defaultStorageProvider *Provider = nil
func InitDefaultStorageProvider() { func InitDefaultStorageProvider() {
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider") defaultStorageProviderStr := conf.GetConfigString("defaultStorageProvider")
if defaultStorageProviderStr != "" { if defaultStorageProviderStr != "" {
defaultStorageProvider = getProvider("admin", 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" return nil, "the user is forbidden to sign in, please contact the administrator"
} }
msg := CheckPassword(user, password) if user.Ldap != "" {
if msg != "" { //ONLY for ldap users
//for ldap users return checkLdapUserPassword(user, password)
if user.Ldap != "" { } else {
return checkLdapUserPassword(user, password) msg := CheckPassword(user, password)
if msg != "" {
return nil, msg
} }
return nil, msg
} }
return user, "" return user, ""
} }

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ type Provider struct {
SubType string `xorm:"varchar(100)" json:"subType"` SubType string `xorm:"varchar(100)" json:"subType"`
Method string `xorm:"varchar(100)" json:"method"` Method string `xorm:"varchar(100)" json:"method"`
ClientId string `xorm:"varchar(100)" json:"clientId"` 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"` ClientId2 string `xorm:"varchar(100)" json:"clientId2"`
ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"` ClientSecret2 string `xorm:"varchar(100)" json:"clientSecret2"`
Cert string `xorm:"varchar(100)" json:"cert"` 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 { func (pi *ProviderItem) IsProviderVisible() bool {
if pi.Provider == nil {
return false
}
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML" return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
} }

View File

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

View File

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

View File

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

View File

@ -25,6 +25,11 @@ import (
type OriginalUser = User type OriginalUser = User
type Credential struct {
Value string `json:"value"`
Salt string `json:"salt"`
}
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) { func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
sql := fmt.Sprintf("select * from %s", syncer.getTable()) sql := fmt.Sprintf("select * from %s", syncer.getTable())
results, err := syncer.Adapter.Engine.QueryString(sql) results, err := syncer.Adapter.Engine.QueryString(sql)

View File

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

View File

@ -15,9 +15,11 @@
package object package object
import ( import (
"encoding/json"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
@ -99,12 +101,18 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
user.PasswordSalt = value user.PasswordSalt = value
case "DisplayName": case "DisplayName":
user.DisplayName = value user.DisplayName = value
case "FirstName":
user.FirstName = value
case "LastName":
user.LastName = value
case "Avatar": case "Avatar":
user.Avatar = syncer.getPartialAvatarUrl(value) user.Avatar = syncer.getPartialAvatarUrl(value)
case "PermanentAvatar": case "PermanentAvatar":
user.PermanentAvatar = value user.PermanentAvatar = value
case "Email": case "Email":
user.Email = value user.Email = value
case "EmailVerified":
user.EmailVerified = util.ParseBool(value)
case "Phone": case "Phone":
user.Phone = value user.Phone = value
case "Location": case "Location":
@ -167,6 +175,32 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
for _, tableColumn := range syncer.TableColumns { for _, tableColumn := range syncer.TableColumns {
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, result[tableColumn.Name]) syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, result[tableColumn.Name])
} }
if syncer.Type == "Keycloak" {
// query and set password and password salt from credential table
sql := fmt.Sprintf("select * from credential where type = 'password' and user_id = '%s'", originalUser.Id)
credentialResult, _ := syncer.Adapter.Engine.QueryString(sql)
if len(credentialResult) > 0 {
credential := Credential{}
_ = json.Unmarshal([]byte(credentialResult[0]["SECRET_DATA"]), &credential)
originalUser.Password = credential.Value
originalUser.PasswordSalt = credential.Salt
}
// query and set signup application from user group table
sql = fmt.Sprintf("select name from keycloak_group where id = " +
"(select group_id as gid from user_group_membership where user_id = '%s')", originalUser.Id)
groupResult, _ := syncer.Adapter.Engine.QueryString(sql)
if len(groupResult) > 0 {
originalUser.SignupApplication = groupResult[0]["name"]
}
// create time
i, _ := strconv.ParseInt(originalUser.CreatedTime, 10, 64)
tm := time.Unix(i/int64(1000), 0)
originalUser.CreatedTime = tm.Format("2006-01-02T15:04:05+08:00")
// enable
originalUser.IsForbidden = !(result["ENABLED"] == "\x01")
}
users = append(users, originalUser) users = append(users, originalUser)
} }
return users return users

View File

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

236
object/token_cas.go Normal file
View File

@ -0,0 +1,236 @@
// 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 object
import (
"encoding/json"
"encoding/xml"
"fmt"
"math/rand"
"sync"
"time"
"github.com/casdoor/casdoor/util"
)
type CasServiceResponse struct {
XMLName xml.Name `xml:"cas:serviceResponse" json:"-"`
Xmlns string `xml:"xmlns:cas,attr"`
Failure *CasAuthenticationFailure
Success *CasAuthenticationSuccess
ProxySuccess *CasProxySuccess
ProxyFailure *CasProxyFailure
}
type CasAuthenticationFailure struct {
XMLName xml.Name `xml:"cas:authenticationFailure" json:"-"`
Code string `xml:"code,attr"`
Message string `xml:",innerxml"`
}
type CasAuthenticationSuccess struct {
XMLName xml.Name `xml:"cas:authenticationSuccess" json:"-"`
User string `xml:"cas:user"`
ProxyGrantingTicket string `xml:"cas:proxyGrantingTicket,omitempty"`
Proxies *CasProxies `xml:"cas:proxies"`
Attributes *CasAttributes `xml:"cas:attributes"`
ExtraAttributes []*CasAnyAttribute `xml:",any"`
}
type CasProxies struct {
XMLName xml.Name `xml:"cas:proxies" json:"-"`
Proxies []string `xml:"cas:proxy"`
}
type CasAttributes struct {
XMLName xml.Name `xml:"cas:attributes" json:"-"`
AuthenticationDate time.Time `xml:"cas:authenticationDate"`
LongTermAuthenticationRequestTokenUsed bool `xml:"cas:longTermAuthenticationRequestTokenUsed"`
IsFromNewLogin bool `xml:"cas:isFromNewLogin"`
MemberOf []string `xml:"cas:memberOf"`
UserAttributes *CasUserAttributes
ExtraAttributes []*CasAnyAttribute `xml:",any"`
}
type CasUserAttributes struct {
XMLName xml.Name `xml:"cas:userAttributes" json:"-"`
Attributes []*CasNamedAttribute `xml:"cas:attribute"`
AnyAttributes []*CasAnyAttribute `xml:",any"`
}
type CasNamedAttribute struct {
XMLName xml.Name `xml:"cas:attribute" json:"-"`
Name string `xml:"name,attr,omitempty"`
Value string `xml:",innerxml"`
}
type CasAnyAttribute struct {
XMLName xml.Name
Value string `xml:",chardata"`
}
type CasAuthenticationSuccessWrapper struct {
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
Service string //to which service this token is issued
}
type CasProxySuccess struct {
XMLName xml.Name `xml:"cas:proxySuccess" json:"-"`
ProxyTicket string `xml:"cas:proxyTicket"`
}
type CasProxyFailure struct {
XMLName xml.Name `xml:"cas:proxyFailure" json:"-"`
Code string `xml:"code,attr"`
Message string `xml:",innerxml"`
}
//st is short for service ticket
var stToServiceResponse sync.Map
//pgt is short for proxy granting ticket
var pgtToServiceResponse sync.Map
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service string) string {
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: token,
Service: service,
})
return pgt
}
func GenerateId() {
panic("unimplemented")
}
func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string) {
if responseWrapperType, ok := pgtToServiceResponse.LoadAndDelete(pgt); ok {
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service
}
return false, nil, ""
}
func GetCasTokenByTicket(ticket string) (bool, *CasAuthenticationSuccess, string) {
if responseWrapperType, ok := stToServiceResponse.LoadAndDelete(ticket); ok {
responseWrapperTypeCast := responseWrapperType.(*CasAuthenticationSuccessWrapper)
return true, responseWrapperTypeCast.AuthenticationSuccess, responseWrapperTypeCast.Service
}
return false, nil, ""
}
func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService string) string {
proxyTicket := fmt.Sprintf("PT-%s", util.GenerateId())
stToServiceResponse.Store(proxyTicket, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: token,
Service: targetService,
})
return proxyTicket
}
func GenerateCasToken(userId string, service string) (string, error) {
if user := GetUser(userId); user != nil {
authenticationSuccess := CasAuthenticationSuccess{
User: user.Name,
Attributes: &CasAttributes{
AuthenticationDate: time.Now(),
UserAttributes: &CasUserAttributes{},
},
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
}
data, _ := json.Marshal(user)
tmp := map[string]string{}
json.Unmarshal(data, &tmp)
for k, v := range tmp {
if v != "" {
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k,
Value: v,
})
}
}
st := fmt.Sprintf("ST-%d", rand.Int())
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: &authenticationSuccess,
Service: service,
})
return st, nil
} else {
return "", fmt.Errorf("invalid user Id")
}
}
func (c *CasAuthenticationSuccess) DeepCopy() CasAuthenticationSuccess {
res := *c
//copy proxy
if c.Proxies != nil {
tmp := c.Proxies.DeepCopy()
res.Proxies = &tmp
}
if c.Attributes != nil {
tmp := c.Attributes.DeepCopy()
res.Attributes = &tmp
}
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
for i, e := range c.ExtraAttributes {
tmp := *e
res.ExtraAttributes[i] = &tmp
}
return res
}
func (c *CasProxies) DeepCopy() CasProxies {
res := CasProxies{
Proxies: make([]string, len(c.Proxies)),
}
copy(res.Proxies, c.Proxies)
return res
}
func (c *CasAttributes) DeepCopy() CasAttributes {
res := *c
if c.MemberOf != nil {
res.MemberOf = make([]string, len(c.MemberOf))
copy(res.MemberOf, c.MemberOf)
}
tmp := c.UserAttributes.DeepCopy()
res.UserAttributes = &tmp
res.ExtraAttributes = make([]*CasAnyAttribute, len(c.ExtraAttributes))
for i, e := range c.ExtraAttributes {
tmp := *e
res.ExtraAttributes[i] = &tmp
}
return res
}
func (c *CasUserAttributes) DeepCopy() CasUserAttributes {
res := CasUserAttributes{
AnyAttributes: make([]*CasAnyAttribute, len(c.AnyAttributes)),
Attributes: make([]*CasNamedAttribute, len(c.Attributes)),
}
for i, a := range c.AnyAttributes {
var tmp = *a
res.AnyAttributes[i] = &tmp
}
for i, a := range c.Attributes {
var tmp = *a
res.Attributes[i] = &tmp
}
return res
}

View File

@ -15,11 +15,10 @@
package object package object
import ( import (
_ "embed"
"fmt" "fmt"
"time" "time"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/golang-jwt/jwt/v4" "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) refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
user.Password = "" user.Password = ""
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
if origin != "" { if origin != "" {
originBackend = origin originBackend = origin

View File

@ -18,7 +18,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/astaxie/beego" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"xorm.io/core" "xorm.io/core"
) )
@ -39,6 +39,7 @@ type User struct {
Avatar string `xorm:"varchar(500)" json:"avatar"` Avatar string `xorm:"varchar(500)" json:"avatar"`
PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"` PermanentAvatar string `xorm:"varchar(500)" json:"permanentAvatar"`
Email string `xorm:"varchar(100) index" json:"email"` Email string `xorm:"varchar(100) index" json:"email"`
EmailVerified bool `json:"emailVerified"`
Phone string `xorm:"varchar(100) index" json:"phone"` Phone string `xorm:"varchar(100) index" json:"phone"`
Location string `xorm:"varchar(100)" json:"location"` Location string `xorm:"varchar(100)" json:"location"`
Address []string `json:"address"` Address []string `json:"address"`
@ -85,6 +86,8 @@ type User struct {
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"` Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
Adfs string `xorm:"adfs varchar(100)" json:"adfs"` Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
Baidu string `xorm:"baidu varchar(100)" json:"baidu"` 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"` Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
Apple string `xorm:"apple varchar(100)" json:"apple"` Apple string `xorm:"apple varchar(100)" json:"apple"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"` AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
@ -303,7 +306,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"} "is_admin", "is_global_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties"}
} }
if isGlobalAdmin { 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) affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols(columns...).Update(user)
@ -428,7 +431,7 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
if user == nil { if user == nil {
return nil, fmt.Errorf("the user: %s doesn't exist", userId) return nil, fmt.Errorf("the user: %s doesn't exist", userId)
} }
origin := beego.AppConfig.String("origin") origin := conf.GetConfigString("origin")
_, originBackend := getOriginFromHost(host) _, originBackend := getOriginFromHost(host)
if origin != "" { if origin != "" {
originBackend = origin originBackend = origin

View File

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

View File

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

View File

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

View File

@ -100,10 +100,17 @@ func willLog(subOwner string, subName string, method string, urlPath string, obj
return true return true
} }
func getUrlPath(urlPath string) string {
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate")) {
return "/cas"
}
return urlPath
}
func AuthzFilter(ctx *context.Context) { func AuthzFilter(ctx *context.Context) {
subOwner, subName := getSubject(ctx) subOwner, subName := getSubject(ctx)
method := ctx.Request.Method method := ctx.Request.Method
urlPath := ctx.Request.URL.Path urlPath := getUrlPath(ctx.Request.URL.Path)
objOwner, objName := getObject(ctx) objOwner, objName := getObject(ctx)
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName) isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)

View File

@ -29,10 +29,8 @@ func AutoSigninFilter(ctx *context.Context) {
// GET parameter like "/page?access_token=123" or // GET parameter like "/page?access_token=123" or
// HTTP Bearer token like "Authorization: Bearer 123" // HTTP Bearer token like "Authorization: Bearer 123"
accessToken := ctx.Input.Query("accessToken") accessToken := util.GetMaxLenStr(ctx.Input.Query("accessToken"), ctx.Input.Query("access_token"), parseBearerToken(ctx))
if accessToken == "" {
accessToken = parseBearerToken(ctx)
}
if accessToken != "" { if accessToken != "" {
token := object.GetTokenByAccessToken(accessToken) token := object.GetTokenByAccessToken(accessToken)
if token == nil { if token == nil {

View File

@ -171,4 +171,10 @@ func initAPI() {
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery") beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks") beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasServiceAndProxyValidate")
beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy")
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
} }

View File

@ -27,6 +27,9 @@ func StaticFilter(ctx *context.Context) {
if strings.HasPrefix(urlPath, "/api/") || strings.HasPrefix(urlPath, "/.well-known/") { if strings.HasPrefix(urlPath, "/api/") || strings.HasPrefix(urlPath, "/.well-known/") {
return return
} }
if strings.HasPrefix(urlPath, "/cas") && (strings.HasSuffix(urlPath, "/serviceValidate") || strings.HasSuffix(urlPath, "/proxy") || strings.HasSuffix(urlPath, "/proxyValidate") || strings.HasSuffix(urlPath, "/validate")) {
return
}
path := "web/build" path := "web/build"
if urlPath == "/" { if urlPath == "/" {

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": { "/api/check-ldap-users-exist": {
"post": { "post": {
"tags": [ "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": { "/api/get-users": {
"get": { "get": {
"tags": [ "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": { "/api/login/oauth/logout": {
"get": { "get": {
"tags": [ "tags": [
@ -2015,7 +2121,6 @@
"in": "query", "in": "query",
"name": "client_secret", "name": "client_secret",
"description": "OAuth client secret", "description": "OAuth client secret",
"required": true,
"type": "string" "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": { "/api/send-verification-code": {
"post": { "post": {
"tags": [ "tags": [
@ -2664,11 +2797,11 @@
} }
}, },
"definitions": { "definitions": {
"2015.0xc0000edb90.false": { "2026.0xc000380de0.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
"2049.0xc0000edbc0.false": { "2060.0xc000380e10.false": {
"title": "false", "title": "false",
"type": "object" "type": "object"
}, },
@ -2685,10 +2818,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/2015.0xc0000edb90.false" "$ref": "#/definitions/2026.0xc000380de0.false"
}, },
"data2": { "data2": {
"$ref": "#/definitions/2049.0xc0000edbc0.false" "$ref": "#/definitions/2060.0xc000380e10.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@ -2709,10 +2842,10 @@
"type": "object", "type": "object",
"properties": { "properties": {
"data": { "data": {
"$ref": "#/definitions/2015.0xc0000edb90.false" "$ref": "#/definitions/2026.0xc000380de0.false"
}, },
"data2": { "data2": {
"$ref": "#/definitions/2049.0xc0000edbc0.false" "$ref": "#/definitions/2060.0xc000380e10.false"
}, },
"msg": { "msg": {
"type": "string" "type": "string"
@ -2864,6 +2997,12 @@
"title": "Cert", "title": "Cert",
"type": "object", "type": "object",
"properties": { "properties": {
"authorityPublicKey": {
"type": "string"
},
"authorityRootPublicKey": {
"type": "string"
},
"bitSize": { "bitSize": {
"type": "integer", "type": "integer",
"format": "int64" "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": { "object.Organization": {
"title": "Organization", "title": "Organization",
"type": "object", "type": "object",
@ -2950,6 +3137,12 @@
"phonePrefix": { "phonePrefix": {
"type": "string" "type": "string"
}, },
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"websiteUrl": { "websiteUrl": {
"type": "string" "type": "string"
} }
@ -2959,19 +3152,19 @@
"title": "Payment", "title": "Payment",
"type": "object", "type": "object",
"properties": { "properties": {
"amount": {
"type": "string"
},
"createdTime": { "createdTime": {
"type": "string" "type": "string"
}, },
"currency": { "currency": {
"type": "string" "type": "string"
}, },
"detail": {
"type": "string"
},
"displayName": { "displayName": {
"type": "string" "type": "string"
}, },
"good": { "message": {
"type": "string" "type": "string"
}, },
"name": { "name": {
@ -2983,12 +3176,31 @@
"owner": { "owner": {
"type": "string" "type": "string"
}, },
"payUrl": {
"type": "string"
},
"price": {
"type": "number",
"format": "double"
},
"productDisplayName": {
"type": "string"
},
"productName": {
"type": "string"
},
"provider": { "provider": {
"type": "string" "type": "string"
}, },
"returnUrl": {
"type": "string"
},
"state": { "state": {
"type": "string" "type": "string"
}, },
"tag": {
"type": "string"
},
"type": { "type": {
"type": "string" "type": "string"
}, },
@ -3074,8 +3286,8 @@
"type": "string" "type": "string"
}, },
"price": { "price": {
"type": "integer", "type": "number",
"format": "int64" "format": "double"
}, },
"providers": { "providers": {
"type": "array", "type": "array",
@ -3087,6 +3299,9 @@
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
}, },
"returnUrl": {
"type": "string"
},
"sold": { "sold": {
"type": "integer", "type": "integer",
"format": "int64" "format": "int64"
@ -3112,6 +3327,9 @@
"category": { "category": {
"type": "string" "type": "string"
}, },
"cert": {
"type": "string"
},
"clientId": { "clientId": {
"type": "string" "type": "string"
}, },
@ -3482,6 +3700,9 @@
"birthday": { "birthday": {
"type": "string" "type": "string"
}, },
"casdoor": {
"type": "string"
},
"createdIp": { "createdIp": {
"type": "string" "type": "string"
}, },

View File

@ -309,6 +309,28 @@ paths:
description: object description: object
schema: schema:
$ref: '#/definitions/Response' $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: /api/check-ldap-users-exist:
post: post:
tags: tags:
@ -1111,6 +1133,35 @@ paths:
responses: responses:
"200": "200":
description: '{int} int The count of filtered users for an organization' 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: /api/get-users:
get: get:
tags: tags:
@ -1262,6 +1313,26 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/object.TokenWrapper' $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: /api/login/oauth/logout:
get: get:
tags: tags:
@ -1318,7 +1389,6 @@ paths:
- in: query - in: query
name: client_secret name: client_secret
description: OAuth client secret description: OAuth client secret
required: true
type: string type: string
responses: responses:
"200": "200":
@ -1336,6 +1406,24 @@ paths:
description: The Response object description: The Response object
schema: schema:
$ref: '#/definitions/controllers.Response' $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: /api/send-verification-code:
post: post:
tags: tags:
@ -1743,10 +1831,10 @@ paths:
schema: schema:
$ref: '#/definitions/object.Userinfo' $ref: '#/definitions/object.Userinfo'
definitions: definitions:
2015.0xc0000edb90.false: 2026.0xc000380de0.false:
title: "false" title: "false"
type: object type: object
2049.0xc0000edbc0.false: 2060.0xc000380e10.false:
title: "false" title: "false"
type: object type: object
RequestForm: RequestForm:
@ -1760,9 +1848,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/2015.0xc0000edb90.false' $ref: '#/definitions/2026.0xc000380de0.false'
data2: data2:
$ref: '#/definitions/2049.0xc0000edbc0.false' $ref: '#/definitions/2060.0xc000380e10.false'
msg: msg:
type: string type: string
name: name:
@ -1776,9 +1864,9 @@ definitions:
type: object type: object
properties: properties:
data: data:
$ref: '#/definitions/2015.0xc0000edb90.false' $ref: '#/definitions/2026.0xc000380de0.false'
data2: data2:
$ref: '#/definitions/2049.0xc0000edbc0.false' $ref: '#/definitions/2060.0xc000380e10.false'
msg: msg:
type: string type: string
name: name:
@ -1880,6 +1968,10 @@ definitions:
title: Cert title: Cert
type: object type: object
properties: properties:
authorityPublicKey:
type: string
authorityRootPublicKey:
type: string
bitSize: bitSize:
type: integer type: integer
format: int64 format: int64
@ -1912,6 +2004,39 @@ definitions:
type: string type: string
value: value:
type: string 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: object.Organization:
title: Organization title: Organization
type: object type: object
@ -1938,21 +2063,25 @@ definitions:
type: string type: string
phonePrefix: phonePrefix:
type: string type: string
tags:
type: array
items:
type: string
websiteUrl: websiteUrl:
type: string type: string
object.Payment: object.Payment:
title: Payment title: Payment
type: object type: object
properties: properties:
amount:
type: string
createdTime: createdTime:
type: string type: string
currency: currency:
type: string type: string
detail:
type: string
displayName: displayName:
type: string type: string
good: message:
type: string type: string
name: name:
type: string type: string
@ -1960,10 +2089,23 @@ definitions:
type: string type: string
owner: owner:
type: string type: string
payUrl:
type: string
price:
type: number
format: double
productDisplayName:
type: string
productName:
type: string
provider: provider:
type: string type: string
returnUrl:
type: string
state: state:
type: string type: string
tag:
type: string
type: type:
type: string type: string
user: user:
@ -2021,8 +2163,8 @@ definitions:
owner: owner:
type: string type: string
price: price:
type: integer type: number
format: int64 format: double
providers: providers:
type: array type: array
items: items:
@ -2030,6 +2172,8 @@ definitions:
quantity: quantity:
type: integer type: integer
format: int64 format: int64
returnUrl:
type: string
sold: sold:
type: integer type: integer
format: int64 format: int64
@ -2047,6 +2191,8 @@ definitions:
type: string type: string
category: category:
type: string type: string
cert:
type: string
clientId: clientId:
type: string type: string
clientId2: clientId2:
@ -2296,6 +2442,8 @@ definitions:
type: string type: string
birthday: birthday:
type: string type: string
casdoor:
type: string
createdIp: createdIp:
type: string type: string
createdTime: createdTime:

View File

@ -20,10 +20,12 @@ import (
) )
var rePhoneCn *regexp.Regexp var rePhoneCn *regexp.Regexp
var rePhone *regexp.Regexp
func init() { func init() {
// https://learnku.com/articles/31543 // https://learnku.com/articles/31543
rePhoneCn, _ = regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`) rePhoneCn, _ = regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`)
rePhone, _ = regexp.Compile("(\\d{3})\\d*(\\d{4})")
} }
func IsEmailValid(email string) bool { func IsEmailValid(email string) bool {
@ -34,3 +36,7 @@ func IsEmailValid(email string) bool {
func IsPhoneCnValid(phone string) bool { func IsPhoneCnValid(phone string) bool {
return rePhoneCn.MatchString(phone) return rePhoneCn.MatchString(phone)
} }
func getMaskedPhone(phone string) string {
return rePhone.ReplaceAllString(phone, "$1****$2")
}

View File

@ -20,7 +20,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"os" "io/ioutil"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -52,6 +52,10 @@ func ParseFloat(s string) float64 {
} }
func ParseBool(s string) bool { func ParseBool(s string) bool {
if s == "\x01" {
return true
}
i := ParseInt(s) i := ParseInt(s)
return i != 0 return i != 0
} }
@ -162,7 +166,7 @@ func GetMinLenStr(strs ...string) string {
} }
func ReadStringFromPath(path string) string { func ReadStringFromPath(path string) string {
data, err := os.ReadFile(path) data, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -171,7 +175,7 @@ func ReadStringFromPath(path string) string {
} }
func WriteStringToPath(s string, path string) { func WriteStringToPath(s string, path string) {
err := os.WriteFile(path, []byte(s), 0644) err := ioutil.WriteFile(path, []byte(s), 0644)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -206,3 +210,28 @@ func IsChinese(str string) bool {
} }
return flag return flag
} }
func GetMaskedPhone(phone string) string {
return getMaskedPhone(phone)
}
func GetMaskedEmail(email string) string {
if email == "" {
return ""
}
tokens := strings.Split(email, "@")
username := maskString(tokens[0])
domain := tokens[1]
domainTokens := strings.Split(domain, ".")
domainTokens[len(domainTokens)-2] = maskString(domainTokens[len(domainTokens)-2])
return fmt.Sprintf("%s@%s", username, strings.Join(domainTokens, "."))
}
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])
}
}

View File

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

View File

@ -18,6 +18,22 @@ module.exports = {
'/.well-known/openid-configuration': { '/.well-known/openid-configuration': {
target: 'http://localhost:8000', target: 'http://localhost:8000',
changeOrigin: true, changeOrigin: true,
},
'/cas/serviceValidate': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/proxyValidate': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/proxy': {
target: 'http://localhost:8000',
changeOrigin: true,
},
'/cas/validate': {
target: 'http://localhost:8000',
changeOrigin: true,
} }
}, },
}, },

View File

@ -68,6 +68,7 @@ import i18next from 'i18next';
import PromptPage from "./auth/PromptPage"; import PromptPage from "./auth/PromptPage";
import OdicDiscoveryPage from "./auth/OidcDiscoveryPage"; import OdicDiscoveryPage from "./auth/OidcDiscoveryPage";
import SamlCallback from './auth/SamlCallback'; import SamlCallback from './auth/SamlCallback';
import CasLogout from "./auth/CasLogout";
const { Header, Footer } = Layout; const { Header, Footer } = Layout;
@ -235,8 +236,12 @@ class App extends Component {
}); });
Setting.showMessage("success", `Logged out successfully`); Setting.showMessage("success", `Logged out successfully`);
let redirectUri = res.data2;
Setting.goToLinkSoft(this, "/"); if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
}else{
Setting.goToLinkSoft(this, "/");
}
} else { } else {
Setting.showMessage("error", `Failed to log out: ${res.msg}`); Setting.showMessage("error", `Failed to log out: ${res.msg}`);
} }
@ -553,22 +558,22 @@ class App extends Component {
</Link> </Link>
) )
} }
<Menu <div>
// theme="dark" <Menu
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"} // theme="dark"
selectedKeys={[`${this.state.selectedMenuKey}`]} mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
style={{lineHeight: '64px', width: '100%', position: 'absolute'}} selectedKeys={[`${this.state.selectedMenuKey}`]}
> style={{lineHeight: '64px', width: '80%', position: 'absolute'}}
>
{
this.renderMenu()
}
</Menu>
{ {
this.renderMenu() this.renderAccount()
} }
<div style = {{float: 'right'}}> <SelectLanguageBox/>
{ </div>
this.renderAccount()
}
<SelectLanguageBox/>
</div>
</Menu>
</Header> </Header>
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}> <Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
<Card className="content-warp-card"> <Card className="content-warp-card">
@ -638,7 +643,8 @@ class App extends Component {
window.location.pathname.startsWith("/login") || window.location.pathname.startsWith("/login") ||
window.location.pathname.startsWith("/callback") || window.location.pathname.startsWith("/callback") ||
window.location.pathname.startsWith("/prompt") || window.location.pathname.startsWith("/prompt") ||
window.location.pathname.startsWith("/forget"); window.location.pathname.startsWith("/forget") ||
window.location.pathname.startsWith("/cas");
} }
renderPage() { renderPage() {
@ -650,6 +656,8 @@ class App extends Component {
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)}/> <Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)}/>
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/> <Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/> <Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account)}} />}/>
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />
<Route exact path="/cas/:owner/:casApplicationName/login" render={(props) => {return (<LoginPage type={"cas"} mode={"signup"} account={this.state.account} {...props} />)}} />
<Route exact path="/callback" component={AuthCallback}/> <Route exact path="/callback" component={AuthCallback}/>
<Route exact path="/callback/saml" component={SamlCallback}/> <Route exact path="/callback/saml" component={SamlCallback}/>
<Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/> <Route exact path="/forget" render={(props) => this.renderHomeIfLoggedIn(<SelfForgetPage {...props} />)}/>

View File

@ -61,7 +61,7 @@ class ApplicationEditPage extends React.Component {
getApplication() { getApplication() {
ApplicationBackend.getApplication("admin", this.state.applicationName) ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((application) => { .then((application) => {
if (application.grantTypes === null || application.grantTypes.length === 0) { if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) {
application.grantTypes = ["authorization_code"]; application.grantTypes = ["authorization_code"];
} }
this.setState({ this.setState({

View File

@ -18,4 +18,4 @@ export const GithubRepo = "https://github.com/casdoor/casdoor";
export const ForceLanguage = ""; export const ForceLanguage = "";
export const DefaultLanguage = "en"; export const DefaultLanguage = "en";
export const EnableExtraPages = false; export const EnableExtraPages = true;

View File

@ -155,7 +155,7 @@ class OrganizationEditPage extends React.Component {
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField('passwordType', value);})}> <Select virtual={false} style={{width: '100%'}} value={this.state.organization.passwordType} onChange={(value => {this.updateOrganizationField('passwordType', value);})}>
{ {
['plain', 'salt', 'md5-salt', 'bcrypt'] ['plain', 'salt', 'md5-salt', 'bcrypt', 'pbkdf2-salt']
.map((item, index) => <Option key={index} value={item}>{item}</Option>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>

View File

@ -83,6 +83,10 @@ class ProductBuyPage extends React.Component {
} }
} }
getPrice(product) {
return `${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`;
}
getProviders(product) { getProviders(product) {
if (this.state.providers.length === 0 || product.providers.length === 0) { if (this.state.providers.length === 0 || product.providers.length === 0) {
return []; return [];
@ -207,7 +211,9 @@ class ProductBuyPage extends React.Component {
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Price")}> <Descriptions.Item label={i18next.t("product:Price")}>
<span style={{fontSize: 28, color: "red", fontWeight: "bold"}}> <span style={{fontSize: 28, color: "red", fontWeight: "bold"}}>
{`${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`} {
this.getPrice(product)
}
</span> </span>
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={i18next.t("product:Quantity")}><span style={{fontSize: 16}}>{product?.quantity}</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'}} > <Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}> <Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} : {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 {authConfig} from "./auth/Auth";
import {Helmet} from "react-helmet"; import {Helmet} from "react-helmet";
import moment from "moment"; import moment from "moment";
import * as Conf from "./Conf";
export let ServerUrl = ""; export let ServerUrl = "";
@ -29,12 +30,17 @@ export let ServerUrl = "";
export const StaticBaseUrl = "https://cdn.casbin.org"; export const StaticBaseUrl = "https://cdn.casbin.org";
// https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html // https://catamphetamine.gitlab.io/country-flag-icons/3x2/index.html
export const CountryRegionData = getCountryRegionData() export const CountryRegionData = getCountryRegionData();
export function getCountryRegionData() { export function getCountryRegionData() {
let language = i18next.language;
if (language === null || language === "null") {
language = Conf.DefaultLanguage;
}
var countries = require("i18n-iso-countries"); var countries = require("i18n-iso-countries");
countries.registerLocale(require("i18n-iso-countries/langs/" + i18next.language + ".json")); countries.registerLocale(require("i18n-iso-countries/langs/" + language + ".json"));
var data = countries.getNames(i18next.language, {select: "official"}); var data = countries.getNames(language, {select: "official"});
var result = [] var result = []
for (var i in data) for (var i in data)
result.push({code:i, name:data[i]}) result.push({code:i, name:data[i]})
@ -70,15 +76,7 @@ export function isProviderVisible(providerItem) {
return false; return false;
} }
if (providerItem.provider.type === "GitHub") { return true;
if (isLocalhost()) {
return providerItem.provider.name.includes("localhost");
} else {
return !providerItem.provider.name.includes("localhost");
}
} else {
return true;
}
} }
export function isProviderVisibleForSignUp(providerItem) { export function isProviderVisibleForSignUp(providerItem) {
@ -404,6 +402,8 @@ export function getProviderTypeOptions(category) {
{id: 'GitLab', name: 'GitLab'}, {id: 'GitLab', name: 'GitLab'},
{id: 'Adfs', name: 'Adfs'}, {id: 'Adfs', name: 'Adfs'},
{id: 'Baidu', name: 'Baidu'}, {id: 'Baidu', name: 'Baidu'},
{id: 'Alipay', name: 'Alipay'},
{id: 'Casdoor', name: 'Casdoor'},
{id: 'Infoflow', name: 'Infoflow'}, {id: 'Infoflow', name: 'Infoflow'},
{id: 'Apple', name: 'Apple'}, {id: 'Apple', name: 'Apple'},
{id: 'AzureAD', name: 'AzureAD'}, {id: 'AzureAD', name: 'AzureAD'},
@ -654,3 +654,94 @@ export function getFromLink() {
} }
return from; return from;
} }
export function getSyncerTableColumns(syncer) {
switch (syncer.type) {
case "Keycloak":
return [
{
"name":"ID",
"type":"string",
"casdoorName":"Id",
"isHashed":true,
"values":[
]
},
{
"name":"USERNAME",
"type":"string",
"casdoorName":"Name",
"isHashed":true,
"values":[
]
},
{
"name":"USERNAME",
"type":"string",
"casdoorName":"DisplayName",
"isHashed":true,
"values":[
]
},
{
"name":"EMAIL",
"type":"string",
"casdoorName":"Email",
"isHashed":true,
"values":[
]
},
{
"name":"EMAIL_VERIFIED",
"type":"boolean",
"casdoorName":"EmailVerified",
"isHashed":true,
"values":[
]
},
{
"name":"FIRST_NAME",
"type":"string",
"casdoorName":"FirstName",
"isHashed":true,
"values":[
]
},
{
"name":"LAST_NAME",
"type":"string",
"casdoorName":"LastName",
"isHashed":true,
"values":[
]
},
{
"name":"CREATED_TIMESTAMP",
"type":"string",
"casdoorName":"CreatedTime",
"isHashed":true,
"values":[
]
},
{
"name":"ENABLED",
"type":"boolean",
"casdoorName":"IsForbidden",
"isHashed":true,
"values":[
]
}
]
default:
return []
}
}

View File

@ -117,9 +117,13 @@ class SyncerEditPage extends React.Component {
{Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} : {Setting.getLabel(i18next.t("provider:Type"), i18next.t("provider:Type - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.syncer.type} onChange={(value => {this.updateSyncerField('type', value);})}> <Select virtual={false} style={{width: '100%'}} value={this.state.syncer.type} onChange={(value => {
this.updateSyncerField('type', value);
this.state.syncer["tableColumns"] = Setting.getSyncerTableColumns(this.state.syncer);
this.state.syncer.table = value === "Keycloak" ? "user_entity" : this.state.syncer.table;
})}>
{ {
['Database', 'LDAP'] ['Database', 'LDAP', 'Keycloak']
.map((item, index) => <Option key={index} value={item}>{item}</Option>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>
@ -198,7 +202,8 @@ class SyncerEditPage extends React.Component {
{Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} : {Setting.getLabel(i18next.t("syncer:Table"), i18next.t("syncer:Table - Tooltip"))} :
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.syncer.table} onChange={e => { <Input value={this.state.syncer.table}
disabled={this.state.syncer.type === "Keycloak"} onChange={e => {
this.updateSyncerField('table', e.target.value); this.updateSyncerField('table', e.target.value);
}} /> }} />
</Col> </Col>

View File

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

View File

@ -98,9 +98,9 @@ class SyncerTableColumnTable extends React.Component {
return ( return (
<Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {this.updateField(table, index, 'casdoorName', value);})}> <Select virtual={false} style={{width: '100%'}} value={text} onChange={(value => {this.updateField(table, index, 'casdoorName', value);})}>
{ {
['Name', 'CreatedTime', 'UpdatedTime', 'Id', 'Type', 'Password', 'PasswordSalt', 'DisplayName', 'Avatar', 'PermanentAvatar', 'Email', 'Phone', ['Name', 'CreatedTime', 'UpdatedTime', 'Id', 'Type', 'Password', 'PasswordSalt', 'DisplayName', 'FirstName', 'LastName', 'Avatar', 'PermanentAvatar',
'Location', 'Address', 'Affiliation', 'Title', 'IdCardType', 'IdCard', 'Homepage', 'Bio', 'Tag', 'Region', 'Language', 'Gender', 'Birthday', 'Email', 'EmailVerified', 'Phone', 'Location', 'Address', 'Affiliation', 'Title', 'IdCardType', 'IdCard', 'Homepage', 'Bio', 'Tag', 'Region',
'Education', 'Score', 'Ranking', 'IsDefaultAvatar', 'IsOnline', 'IsAdmin', 'IsGlobalAdmin', 'IsForbidden', 'IsDeleted', 'CreatedIp'] 'Language', 'Gender', 'Birthday', 'Education', 'Score', 'Ranking', 'IsDefaultAvatar', 'IsOnline', 'IsAdmin', 'IsGlobalAdmin', 'IsForbidden', 'IsDeleted', 'CreatedIp']
.map((item, index) => <Option key={index} value={item}>{item}</Option>) .map((item, index) => <Option key={index} value={item}>{item}</Option>)
} }
</Select> </Select>

View File

@ -224,7 +224,11 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} : {Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
</Col> </Col>
<Col style={{paddingRight: '20px'}} span={11} > <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>
<Col span={11} > <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} { 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"))} : {Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
</Col> </Col>
<Col style={{paddingRight: '20px'}} span={11} > <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>
<Col span={11} > <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} { 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

@ -62,6 +62,14 @@ export function login(values, oAuthParams) {
}).then(res => res.json()); }).then(res => res.json());
} }
export function loginCas(values, params) {
return fetch(`${authConfig.serverUrl}/api/login?service=${params.service}`, {
method: 'POST',
credentials: "include",
body: JSON.stringify(values),
}).then(res => res.json());
}
export function logout() { export function logout() {
return fetch(`${authConfig.serverUrl}/api/logout`, { return fetch(`${authConfig.serverUrl}/api/logout`, {
method: 'POST', method: 'POST',

View File

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

68
web/src/auth/CasLogout.js Normal file
View File

@ -0,0 +1,68 @@
// 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 React from "react";
import {Spin} from "antd";
import {withRouter} from "react-router-dom";
import * as AuthBackend from "./AuthBackend";
import * as Setting from "../Setting";
import i18next from "i18next";
class CasLogout extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
msg: null,
};
if (props.match?.params.casApplicationName !== undefined) {
this.state.owner = props.match?.params.owner
this.state.applicationName = props.match?.params.casApplicationName
}
}
UNSAFE_componentWillMount() {
const params = new URLSearchParams(this.props.location.search);
AuthBackend.logout()
.then((res) => {
if (res.status === 'ok') {
Setting.showMessage("success", `Logged out successfully`);
this.props.clearAccount()
let redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
} else if (params.has("service")) {
Setting.goToLink(params.get("service"))
} else {
Setting.goToLinkSoft(this, `/cas/${this.state.owner}/${this.state.applicationName}/login`);
}
} else {
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
}
});
}
render() {
return (
<div style={{textAlign: "center"}}>
{
<Spin size="large" tip={i18next.t("login:Logging out...")} style={{paddingTop: "10%"}} />
}
</div>
)
}
}
export default withRouter(CasLogout);

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

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import React from "react"; import React from "react";
import {Button, Col, Form, Select, Input, Row, Steps} from "antd"; import {Button, Col, Form, Input, Row, Select, Steps} from "antd";
import * as AuthBackend from "./AuthBackend"; import * as AuthBackend from "./AuthBackend";
import * as ApplicationBackend from "../backend/ApplicationBackend"; import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Util from "./Util"; import * as Util from "./Util";
@ -43,6 +43,7 @@ class ForgetPage extends React.Component {
msg: null, msg: null,
userId: "", userId: "",
username: "", username: "",
name: "",
email: "", email: "",
isFixed: false, isFixed: false,
fixedContent: "", fixedContent: "",
@ -100,7 +101,7 @@ class ForgetPage extends React.Component {
if (res.status === "ok") { if (res.status === "ok") {
const phone = res.data.phone; const phone = res.data.phone;
const email = res.data.email; const email = res.data.email;
this.setState({phone: phone, email: email, username: res.data.name}); this.setState({phone: phone, email: email, username: res.data.name, name: res.data.name});
if (phone !== "" && email === "") { if (phone !== "" && email === "") {
this.setState({ this.setState({
@ -134,15 +135,16 @@ class ForgetPage extends React.Component {
break; break;
case "step2": case "step2":
const oAuthParams = Util.getOAuthGetParameters(); const oAuthParams = Util.getOAuthGetParameters();
if(this.state.verifyType=="email"){ if (this.state.verifyType === "email") {
this.setState({username: this.state.email}) this.setState({username: this.state.email})
}else if(this.state.verifyType=="phone"){ } else if (this.state.verifyType === "phone") {
this.setState({username: this.state.phone}) this.setState({username: this.state.phone})
} }
AuthBackend.login({ AuthBackend.login({
application: forms.step2.getFieldValue("application"), application: forms.step2.getFieldValue("application"),
organization: forms.step2.getFieldValue("organization"), organization: forms.step2.getFieldValue("organization"),
username: this.state.username, username: this.state.username,
name: this.state.name,
code: forms.step2.getFieldValue("emailCode"), code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix, phonePrefix: this.state.application?.organizationObj.phonePrefix,
type: "login" type: "login"
@ -179,7 +181,7 @@ class ForgetPage extends React.Component {
if (this.state.phone !== "") { if (this.state.phone !== "") {
options.push( options.push(
<Option key={"phone"} value={"phone"}> <Option key={"phone"} value={"phone"}>
&nbsp;&nbsp;{Setting.getMaskedPhone(this.state.phone)} &nbsp;&nbsp;{this.state.phone}
</Option> </Option>
); );
} }
@ -187,7 +189,7 @@ class ForgetPage extends React.Component {
if (this.state.email !== "") { if (this.state.email !== "") {
options.push( options.push(
<Option key={"email"} value={"email"}> <Option key={"email"} value={"email"}>
&nbsp;&nbsp;{Setting.getMaskedEmail(this.state.email)} &nbsp;&nbsp;{this.state.email}
</Option> </Option>
); );
} }
@ -349,12 +351,12 @@ class ForgetPage extends React.Component {
{this.state.verifyType === "email" ? ( {this.state.verifyType === "email" ? (
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} disabled={this.state.username === "" || this.state.verifyType === ""}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationOrgName(this.state.application)]} onButtonClickArgs={[this.state.email, "email", Setting.getApplicationOrgName(this.state.application), this.state.name]}
/> />
) : ( ) : (
<CountDownInput <CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""} disabled={this.state.username === "" || this.state.verifyType === ""}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationOrgName(this.state.application)]} onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationOrgName(this.state.application), this.state.name]}
/> />
)} )}
</Form.Item> </Form.Item>

View File

@ -36,6 +36,8 @@ import LarkLoginButton from "./LarkLoginButton";
import GitLabLoginButton from "./GitLabLoginButton"; import GitLabLoginButton from "./GitLabLoginButton";
import AdfsLoginButton from "./AdfsLoginButton"; import AdfsLoginButton from "./AdfsLoginButton";
import BaiduLoginButton from "./BaiduLoginButton"; import BaiduLoginButton from "./BaiduLoginButton";
import AlipayLoginButton from "./AlipayLoginButton";
import CasdoorLoginButton from "./CasdoorLoginButton";
import InfoflowLoginButton from "./InfoflowLoginButton"; import InfoflowLoginButton from "./InfoflowLoginButton";
import AppleLoginButton from "./AppleLoginButton" import AppleLoginButton from "./AppleLoginButton"
import AzureADLoginButton from "./AzureADLoginButton"; import AzureADLoginButton from "./AzureADLoginButton";
@ -56,12 +58,19 @@ class LoginPage extends React.Component {
isCodeSignin: false, isCodeSignin: false,
msg: null, msg: null,
username: null, username: null,
validEmailOrPhone: false validEmailOrPhone: false,
validEmail: false,
validPhone: false,
owner: null,
}; };
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
this.state.owner = props.match?.params.owner
this.state.applicationName = props.match?.params.casApplicationName
}
} }
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
if (this.state.type === "login") { if (this.state.type === "login" || this.state.type === "cas") {
this.getApplication(); this.getApplication();
} else if (this.state.type === "code") { } else if (this.state.type === "code") {
this.getApplicationLogin(); this.getApplicationLogin();
@ -116,58 +125,85 @@ class LoginPage extends React.Component {
onFinish(values) { onFinish(values) {
const application = this.getApplicationObj(); const application = this.getApplicationObj();
const ths = this; const ths = this;
const oAuthParams = Util.getOAuthGetParameters();
if (oAuthParams !== null && oAuthParams.responseType!= null && oAuthParams.responseType !== "") { //here we are supposed to judge whether casdoor is working as a oauth server or CAS server
values["type"] = oAuthParams.responseType if (this.state.type === "cas") {
}else{ //cas
const casParams = Util.getCasParameters()
values["type"] = this.state.type; values["type"] = this.state.type;
} AuthBackend.loginCas(values, casParams).then((res) => {
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
AuthBackend.login(values, oAuthParams)
.then((res) => {
if (res.status === 'ok') { if (res.status === 'ok') {
const responseType = values["type"]; let msg = "Logged in successfully. "
if (responseType === "login") { if (casParams.service == "") {
Util.showMessage("success", `Logged in successfully`); //If service was not specified, CAS MUST display a message notifying the client that it has successfully initiated a single sign-on session.
msg += "Now you can visit apps protected by casdoor."
const link = Setting.getFromLink();
Setting.goToLink(link);
} else if (responseType === "code") {
const code = res.data;
if (Setting.hasPromptPage(application)) {
AuthBackend.getAccount("")
.then((res) => {
let account = null;
if (res.status === "ok") {
account = res.data;
account.organization = res.data2;
this.onUpdateAccount(account);
if (Setting.isPromptAnswered(account, application)) {
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
} else {
Setting.goToLinkSoft(ths, `/prompt/${application.name}?redirectUri=${oAuthParams.redirectUri}&code=${code}&state=${oAuthParams.state}`);
}
} else {
Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
}
});
} else {
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
}
// Util.showMessage("success", `Authorization code: ${res.data}`);
} else if (responseType === "token" || responseType === "id_token") {
const accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
} }
Util.showMessage("success", msg);
if (casParams.service !== "") {
let st = res.data
window.location.href = casParams.service + "?ticket=" + st
}
} else { } else {
Util.showMessage("error", `Failed to log in: ${res.msg}`); Util.showMessage("error", `Failed to log in: ${res.msg}`);
} }
}); })
} else {
//oauth
const oAuthParams = Util.getOAuthGetParameters();
if (oAuthParams !== null && oAuthParams.responseType != null && oAuthParams.responseType !== "") {
values["type"] = oAuthParams.responseType
}else{
values["type"] = this.state.type;
}
values["phonePrefix"] = this.getApplicationObj()?.organizationObj.phonePrefix;
AuthBackend.login(values, oAuthParams)
.then((res) => {
if (res.status === 'ok') {
const responseType = values["type"];
if (responseType === "login") {
Util.showMessage("success", `Logged in successfully`);
const link = Setting.getFromLink();
Setting.goToLink(link);
} else if (responseType === "code") {
const code = res.data;
const concatChar = oAuthParams?.redirectUri?.includes('?') ? '&' : '?';
if (Setting.hasPromptPage(application)) {
AuthBackend.getAccount("")
.then((res) => {
let account = null;
if (res.status === "ok") {
account = res.data;
account.organization = res.data2;
this.onUpdateAccount(account);
if (Setting.isPromptAnswered(account, application)) {
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}`);
}
} else {
Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
}
});
} else {
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 accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}?state=${oAuthParams.state}&token_type=bearer`);
}
} else {
Util.showMessage("error", `Failed to log in: ${res.msg}`);
}
});
}
}; };
getSigninButton(type) { getSigninButton(type) {
@ -198,8 +234,12 @@ class LoginPage extends React.Component {
return <GitLabLoginButton text={text} align={"center"} /> return <GitLabLoginButton text={text} align={"center"} />
} else if (type === "Adfs") { } else if (type === "Adfs") {
return <AdfsLoginButton text={text} align={"center"} /> return <AdfsLoginButton text={text} align={"center"} />
} else if (type === "Casdoor") {
return <CasdoorLoginButton text={text} align={"center"} />
} else if (type === "Baidu") { } else if (type === "Baidu") {
return <BaiduLoginButton text={text} align={"center"} /> return <BaiduLoginButton text={text} align={"center"} />
} else if (type === "Alipay") {
return <AlipayLoginButton text={text} align={"center"} />
} else if (type === "Infoflow") { } else if (type === "Infoflow") {
return <InfoflowLoginButton text={text} align={"center"} /> return <InfoflowLoginButton text={text} align={"center"} />
} else if (type === "Apple") { } else if (type === "Apple") {
@ -344,6 +384,12 @@ class LoginPage extends React.Component {
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!")); 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}); this.setState({validEmailOrPhone: true});
return Promise.resolve(); return Promise.resolve();
} }
@ -369,7 +415,7 @@ class LoginPage extends React.Component {
> >
<CountDownInput <CountDownInput
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone} 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> </Form.Item>
) : ( ) : (

View File

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

View File

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

View File

@ -66,7 +66,7 @@ class ResultPage extends React.Component {
extra={[ extra={[
<Button type="primary" key="login" onClick={() => { <Button type="primary" key="login" onClick={() => {
let linkInStorage = sessionStorage.getItem("loginURL") let linkInStorage = sessionStorage.getItem("loginURL")
if (linkInStorage != "") { if (linkInStorage !== null && linkInStorage !== "") {
Setting.goToLink(linkInStorage) Setting.goToLink(linkInStorage)
} else { } else {
Setting.goToLogin(this, application) Setting.goToLogin(this, application)

View File

@ -558,7 +558,7 @@ class SignupPage extends React.Component {
&nbsp;&nbsp;{i18next.t("signup:Have account?")}&nbsp; &nbsp;&nbsp;{i18next.t("signup:Have account?")}&nbsp;
<a onClick={() => { <a onClick={() => {
let linkInStorage = sessionStorage.getItem("loginURL") let linkInStorage = sessionStorage.getItem("loginURL")
if(linkInStorage != ""){ if(linkInStorage != null){
Setting.goToLink(linkInStorage) Setting.goToLink(linkInStorage)
}else{ }else{
Setting.goToLogin(this, application) Setting.goToLogin(this, application)

View File

@ -79,6 +79,18 @@ function getRefinedValue(value){
return (value === null)? "" : value return (value === null)? "" : value
} }
export function getCasParameters(params){
const queries = (params !== undefined) ? params : new URLSearchParams(window.location.search);
const service = getRefinedValue(queries.get("service"))
const renew = getRefinedValue(queries.get("renew"))
const gateway = getRefinedValue(queries.get("gateway"))
return {
service: service,
renew: renew,
gateway: gateway,
}
}
export function getOAuthGetParameters(params) { export function getOAuthGetParameters(params) {
const queries = (params !== undefined) ? params : new URLSearchParams(window.location.search); const queries = (params !== undefined) ? params : new URLSearchParams(window.location.search);
const clientId = getRefinedValue(queries.get("client_id")); const clientId = getRefinedValue(queries.get("client_id"));

View File

@ -49,14 +49,6 @@ export const CountDownInput = (props) => {
const handleOk = () => { const handleOk = () => {
setVisible(false); setVisible(false);
if (isValidEmail(onButtonClickArgs[0])) {
onButtonClickArgs[1] = "email";
} else if (isValidPhone(onButtonClickArgs[0])) {
onButtonClickArgs[1] = "phone";
} else {
Util.showMessage("error", i18next.t("login:Invalid Email or phone"))
return;
}
setButtonLoading(true) setButtonLoading(true)
UserBackend.sendCode(checkType, checkId, key, ...onButtonClickArgs).then(res => { UserBackend.sendCode(checkType, checkId, key, ...onButtonClickArgs).then(res => {
setKey(""); setKey("");

View File

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

BIN
xlsx/user_test.xlsx Normal file

Binary file not shown.

View File

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