Compare commits

...

28 Commits

Author SHA1 Message Date
b94d06fb07 feat: add some Radius protocol code (#2351)
* feat: add radius server

* feat: parse org from packet

* feat: add comment

* Update main.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-09-24 16:50:31 +08:00
f9cc6ed064 Add groups to role 2023-09-24 10:17:18 +08:00
4cc9137637 Improve permission, adapter page UI 2023-09-24 09:56:06 +08:00
d145ab780c feat: fix wrong elements in getPermissionsByUser() related functions 2023-09-24 09:13:54 +08:00
687830697e Refactor getPermissionsAndRolesByUser() related code 2023-09-24 08:08:32 +08:00
111d1a5786 Use UserInfo's ID in OAuth login 2023-09-23 00:13:13 +08:00
775dd9eb57 Improve email provider error handling and fix bug 2023-09-21 23:11:58 +08:00
8f6c295c40 fix: empty AzureAD tenant id (#2349) 2023-09-21 08:34:23 +08:00
2f31e35315 feat: update casbin to 2.77.2 (#2345)
* fix: make redirect_uri really optional in logout route

* feat: update casbin to 2.77.2
2023-09-20 23:37:55 +08:00
b6d6aa9d04 Use GenerateIdForNewUser() in add-user API 2023-09-20 22:50:17 +08:00
f40d44fa1c Refactor out GenerateIdForNewUser() 2023-09-20 22:45:00 +08:00
3b2820cbe3 feat: make redirect_uri really optional in logout route (#2342) 2023-09-18 21:47:49 +08:00
764e88f603 Change MFA issuer 2023-09-18 17:40:11 +08:00
7f298efebc feat: fix Apple OAuth issue (#2338)
* feat: fix sign in with apple bug

* fix username
2023-09-18 17:04:03 +08:00
0fc48bb6cd Remove escapePath() to fix Unicode resource filenames 2023-09-17 21:31:22 +08:00
c3b3840994 fix: fix update score permission check (#2335)
* fix: Fixed the missing permission check when updating the score field.
* Update object/user_util.go
2023-09-16 21:06:20 +08:00
eacc3fae5a fix: handle more errors in downloadImage() 2023-09-15 22:53:09 +08:00
ce7a2e924b feat: fix XML format issue in GenerateCasToken() 2023-09-15 22:38:04 +08:00
ece060d03d feat: fix XML bug in GenerateCasToken() 2023-09-15 18:57:59 +08:00
1276da4daa Check old password for normal user in SetPassword() 2023-09-15 10:21:02 +08:00
616629ef99 Refactor CheckLoginPermission() code 2023-09-15 02:47:53 +08:00
b633ecdcf2 Fix bug that cannot access application's public certificate for non "admin" owner 2023-09-15 00:56:40 +08:00
a12ba7fb85 feat: allow CORS for UserInfo API in OIDC (#2313) 2023-09-13 18:11:13 +08:00
08a0092974 feat: fix alipay payment provider (#2330)
* feat: support alipay payment provider

* feat: update notify params

* feat: update root cert

* feat: update ProviderEditPage.js

* feat: gofumpt
2023-09-13 17:30:51 +08:00
bb04b10e8b Fix JSON issue in GenerateCasToken() 2023-09-13 16:45:11 +08:00
ea1414dfd0 Fix typo 2023-09-13 00:19:32 +08:00
32a8a028d5 Set TOTP issuer to Casdoor 2023-09-12 23:56:39 +08:00
0fe34c2f53 Fix the issue that database syncer can't work with null-fields on source table 2023-09-12 16:06:44 +08:00
56 changed files with 708 additions and 294 deletions

View File

@ -46,6 +46,7 @@ p, *, *, POST, /api/login, *, *
p, *, *, GET, /api/get-app-login, *, * p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, * p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/logout, *, * p, *, *, GET, /api/logout, *, *
p, *, *, POST, /api/callback, *, *
p, *, *, GET, /api/get-account, *, * p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, * p, *, *, GET, /api/userinfo, *, *
p, *, *, GET, /api/user, *, * p, *, *, GET, /api/user, *, *

View File

@ -20,6 +20,7 @@ staticBaseUrl = "https://cdn.casbin.org"
isDemoMode = false isDemoMode = false
batchSize = 100 batchSize = 100
ldapServerPort = 389 ldapServerPort = 389
radiusServerPort = 1812
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1} quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"} logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
initDataFile = "./init_data.json" initDataFile = "./init_data.json"

View File

@ -18,7 +18,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"strings" "strings"
"github.com/casdoor/casdoor/form" "github.com/casdoor/casdoor/form"
@ -119,20 +118,10 @@ func (c *ApiController) Signup() {
} }
} }
id := util.GenerateId() id, err := object.GenerateIdForNewUser(application)
if application.GetSignupItemRule("ID") == "Incremental" { if err != nil {
lastUser, err := object.GetLastUser(authForm.Organization) c.ResponseError(err.Error())
if err != nil { return
c.ResponseError(err.Error())
return
}
lastIdInt := -1
if lastUser != nil {
lastIdInt = util.ParseInt(lastUser.Id)
}
id = strconv.Itoa(lastIdInt + 1)
} }
username := authForm.Username username := authForm.Username
@ -309,27 +298,32 @@ func (c *ApiController) Logout() {
return return
} }
if application.IsRedirectUriValid(redirectUri) { if user == "" {
if user == "" { user = util.GetId(token.Organization, token.User)
user = util.GetId(token.Organization, token.User) }
}
c.ClearUserSession() c.ClearUserSession()
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265 // TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
owner, username := util.GetOwnerAndNameFromId(user) owner, username := util.GetOwnerAndNameFromId(user)
_, err := object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID()) _, err = object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return
}
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
if redirectUri == "" {
c.ResponseOk()
return
} else {
if application.IsRedirectUriValid(redirectUri) {
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
} else {
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
return return
} }
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
} else {
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
return
} }
} }
} }

View File

@ -90,14 +90,24 @@ func (c *ApiController) GetApplication() {
return return
} }
if c.Input().Get("withKey") != "" && application.Cert != "" { if c.Input().Get("withKey") != "" && application != nil && application.Cert != "" {
cert, err := object.GetCert(util.GetId(application.Owner, application.Cert)) cert, err := object.GetCert(util.GetId(application.Owner, application.Cert))
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
application.CertPublicKey = cert.Certificate if cert == nil {
cert, err = object.GetCert(util.GetId(application.Organization, application.Cert))
if err != nil {
c.ResponseError(err.Error())
return
}
}
if cert != nil {
application.CertPublicKey = cert.Certificate
}
} }
c.ResponseOk(object.GetMaskedApplication(application, userId)) c.ResponseOk(object.GetMaskedApplication(application, userId))

View File

@ -20,6 +20,7 @@ import (
"encoding/xml" "encoding/xml"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
@ -59,7 +60,7 @@ func tokenToResponse(token *object.Token) *Response {
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) { func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
userId := user.GetId() userId := user.GetId()
allowed, err := object.CheckAccessPermission(userId, application) allowed, err := object.CheckLoginPermission(userId, application)
if err != nil { if err != nil {
c.ResponseError(err.Error(), nil) c.ResponseError(err.Error(), nil)
return return
@ -614,11 +615,16 @@ func (c *ApiController) Login() {
return return
} }
userId := userInfo.Id
if userId == "" {
userId = util.GenerateId()
}
user = &object.User{ user = &object.User{
Owner: application.Organization, Owner: application.Organization,
Name: userInfo.Username, Name: userInfo.Username,
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
Id: util.GenerateId(), Id: userId,
Type: "normal-user", Type: "normal-user",
DisplayName: userInfo.DisplayName, DisplayName: userInfo.DisplayName,
Avatar: userInfo.AvatarUrl, Avatar: userInfo.AvatarUrl,
@ -896,3 +902,16 @@ func (c *ApiController) GetCaptchaStatus() {
} }
c.ResponseOk(captchaEnabled) c.ResponseOk(captchaEnabled)
} }
// Callback
// @Title Callback
// @Tag Callback API
// @Description Get Login Error Counts
// @router /api/Callback [post]
func (c *ApiController) Callback() {
code := c.GetString("code")
state := c.GetString("state")
frontendCallbackUrl := fmt.Sprintf("/callback?code=%s&state=%s", code, state)
c.Ctx.Redirect(http.StatusFound, frontendCallbackUrl)
}

View File

@ -179,7 +179,7 @@ func (c *ApiController) NotifyPayment() {
body := c.Ctx.Input.RequestBody body := c.Ctx.Input.RequestBody
payment, err := object.NotifyPayment(c.Ctx.Request, body, owner, paymentName) payment, err := object.NotifyPayment(body, owner, paymentName)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@ -156,7 +156,7 @@ func (c *ApiController) DeleteToken() {
// @Success 200 {object} object.TokenWrapper The Response object // @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object // @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object // @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/access_token [post] // @router api/login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() { func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type") grantType := c.Input().Get("grant_type")
refreshToken := c.Input().Get("refresh_token") refreshToken := c.Input().Get("refresh_token")

View File

@ -457,7 +457,16 @@ func (c *ApiController) SetPassword() {
return return
} }
if oldPassword != "" { isAdmin := c.IsAdmin()
if isAdmin {
if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
return
}
}
} else {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage()) msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" { if msg != "" {
c.ResponseError(msg) c.ResponseError(msg)

View File

@ -123,7 +123,9 @@ func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
bodyBuffer := bytes.NewBuffer(postBody) bodyBuffer := bytes.NewBuffer(postBody)
req, err := http.NewRequest("POST", a.Endpoint+sendEmailEndpoint+"?api-version="+apiVersion, bodyBuffer) endpoint := strings.TrimSuffix(a.Endpoint, "/")
url := fmt.Sprintf("%s/emails:send?api-version=2023-03-31", endpoint)
req, err := http.NewRequest("POST", url, bodyBuffer)
if err != nil { if err != nil {
return fmt.Errorf("error creating AzureACS API request: %s", err) return fmt.Errorf("error creating AzureACS API request: %s", err)
} }
@ -149,7 +151,7 @@ func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
defer resp.Body.Close() defer resp.Body.Close()
// Response error Handling // Response error Handling
if resp.StatusCode == http.StatusBadRequest { if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnauthorized {
commError := ErrorResponse{} commError := ErrorResponse{}
err = json.NewDecoder(resp.Body).Decode(&commError) err = json.NewDecoder(resp.Body).Decode(&commError)

4
go.mod
View File

@ -10,7 +10,7 @@ require (
github.com/beego/beego v1.12.12 github.com/beego/beego v1.12.12
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/casbin/casbin v1.9.1 // indirect github.com/casbin/casbin v1.9.1 // indirect
github.com/casbin/casbin/v2 v2.37.0 github.com/casbin/casbin/v2 v2.77.2
github.com/casdoor/go-sms-sender v0.14.0 github.com/casdoor/go-sms-sender v0.14.0
github.com/casdoor/gomail/v2 v2.0.1 github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/notify v0.43.0 github.com/casdoor/notify v0.43.0
@ -54,6 +54,7 @@ require (
github.com/stripe/stripe-go/v74 v74.29.0 github.com/stripe/stripe-go/v74 v74.29.0
github.com/tealeg/xlsx v1.0.5 github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4 github.com/thanhpk/randstr v1.0.4
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/xorm-io/builder v0.3.13 github.com/xorm-io/builder v0.3.13
github.com/xorm-io/core v0.7.4 github.com/xorm-io/core v0.7.4
@ -65,6 +66,7 @@ require (
google.golang.org/api v0.138.0 google.golang.org/api v0.138.0
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68 // indirect
maunium.net/go/mautrix v0.16.0 maunium.net/go/mautrix v0.16.0
modernc.org/sqlite v1.18.2 modernc.org/sqlite v1.18.2
) )

7
go.sum
View File

@ -919,6 +919,8 @@ github.com/casbin/casbin/v2 v2.1.0/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n
github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/casbin/casbin/v2 v2.28.3/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw= github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw=
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM=
github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk=
github.com/casdoor/go-sms-sender v0.14.0 h1:yqrzWIHUg64OYPynzF5Fr0XDuCWIWxtXIjOQAAkRKuw= github.com/casdoor/go-sms-sender v0.14.0 h1:yqrzWIHUg64OYPynzF5Fr0XDuCWIWxtXIjOQAAkRKuw=
github.com/casdoor/go-sms-sender v0.14.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs= github.com/casdoor/go-sms-sender v0.14.0/go.mod h1:cQs7qqohMJBgIVZebOCB8ko09naG1vzFJEH59VNIscs=
github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w= github.com/casdoor/gomail/v2 v2.0.1 h1:J+FG6x80s9e5lBHUn8Sv0Y56mud34KiWih5YdmudR/w=
@ -1795,6 +1797,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw= github.com/tklauser/go-sysconf v0.3.10 h1:IJ1AZGZRWbY8T5Vfk04D9WOA5WSejdflXxP03OUqALw=
@ -1908,6 +1912,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -2764,6 +2769,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68 h1:2NDro2Jzkrqfngy/sA5GVnChs7fx8EzcQKFi/lI2cfg=
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68/go.mod h1:pFWM9De99EY9TPVyHIyA56QmoRViVck/x41WFkUlc9A=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=

View File

@ -19,6 +19,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"reflect" "reflect"
"strings"
"time" "time"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@ -97,6 +98,9 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
Session: &amazon.Session{}, Session: &amazon.Session{},
} }
case "Apple": case "Apple":
if !strings.Contains(redirectUrl, "/api/callback") {
redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1)
}
idp = GothIdProvider{ idp = GothIdProvider{
Provider: apple.New(clientId, clientSecret, redirectUrl, nil), Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
Session: &apple.Session{}, Session: &apple.Session{},
@ -392,7 +396,9 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
// Goth's idp all implement the Client method, but since the goth.Provider interface does not provide to modify idp's client method, reflection is required // Goth's idp all implement the Client method, but since the goth.Provider interface does not provide to modify idp's client method, reflection is required
func (idp *GothIdProvider) SetHttpClient(client *http.Client) { func (idp *GothIdProvider) SetHttpClient(client *http.Client) {
idpClient := reflect.ValueOf(idp.Provider).Elem().FieldByName("HTTPClient") idpClient := reflect.ValueOf(idp.Provider).Elem().FieldByName("HTTPClient")
idpClient.Set(reflect.ValueOf(client)) if idpClient.IsValid() {
idpClient.Set(reflect.ValueOf(client))
}
} }
func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) { func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
@ -468,6 +474,8 @@ func getUser(gothUser goth.User, provider string) *UserInfo {
if provider == "steam" { if provider == "steam" {
user.Username = user.Id user.Username = user.Id
user.Email = "" user.Email = ""
} else if provider == "apple" {
user.Username = util.GetUsernameFromEmail(user.Email)
} }
return &user return &user
} }

View File

@ -25,6 +25,7 @@ import (
"github.com/casdoor/casdoor/ldap" "github.com/casdoor/casdoor/ldap"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/radius"
"github.com/casdoor/casdoor/routers" "github.com/casdoor/casdoor/routers"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
@ -81,6 +82,7 @@ func main() {
logs.SetLogFuncCall(false) logs.SetLogFuncCall(false)
go ldap.StartLdapServer() go ldap.StartLdapServer()
go radius.StartRadiusServer()
go object.ClearThroughputPerSecond() go object.ClearThroughputPerSecond()
beego.Run(fmt.Sprintf(":%v", port)) beego.Run(fmt.Sprintf(":%v", port))

View File

@ -33,10 +33,8 @@ type Cert struct {
BitSize int `json:"bitSize"` BitSize int `json:"bitSize"`
ExpireInYears int `json:"expireInYears"` ExpireInYears int `json:"expireInYears"`
Certificate string `xorm:"mediumtext" json:"certificate"` Certificate string `xorm:"mediumtext" json:"certificate"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"` PrivateKey string `xorm:"mediumtext" json:"privateKey"`
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
} }
func GetMaskedCert(cert *Cert) *Cert { func GetMaskedCert(cert *Cert) *Cert {

BIN
object/cert.go~ Normal file

Binary file not shown.

View File

@ -350,7 +350,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
return hasPermission, fmt.Errorf(i18n.Translate(lang, "auth:Unauthorized operation")) return hasPermission, fmt.Errorf(i18n.Translate(lang, "auth:Unauthorized operation"))
} }
func CheckAccessPermission(userId string, application *Application) (bool, error) { func CheckLoginPermission(userId string, application *Application) (bool, error) {
var err error var err error
if userId == "built-in/admin" { if userId == "built-in/admin" {
return true, nil return true, nil
@ -361,32 +361,40 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
return false, err return false, err
} }
allowed := true allowCount := 0
denyCount := 0
for _, permission := range permissions { for _, permission := range permissions {
if !permission.IsEnabled { if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
continue continue
} }
isHit := false if permission.isUserHit(userId) {
for _, resource := range permission.Resources { allowCount += 1
if application.Name == resource {
isHit = true
break
}
} }
if isHit { enforcer := getPermissionEnforcer(permission)
containsAsterisk := ContainsAsterisk(userId, permission.Users)
if containsAsterisk { var isAllowed bool
return true, err isAllowed, err = enforcer.Enforce(userId, application.Name, "Read")
if err != nil {
return false, err
}
if isAllowed {
if permission.Effect == "Allow" {
allowCount += 1
} }
enforcer := getPermissionEnforcer(permission) } else {
if allowed, err = enforcer.Enforce(userId, application.Name, "read"); allowed { if permission.Effect == "Deny" {
return allowed, err denyCount += 1
} }
} }
} }
return allowed, err
if denyCount > 0 {
return false, nil
}
return true, nil
} }
func CheckUsername(username string, lang string) string { func CheckUsername(username string, lang string) string {

View File

@ -226,7 +226,7 @@ func GetGroupUserCount(groupId string, field, value string) (int64, error) {
} else { } else {
return ormer.Engine.Table("user"). return ormer.Engine.Table("user").
Where("owner = ?", owner).In("name", names). Where("owner = ?", owner).In("name", names).
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%"). And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count() Count()
} }
} }
@ -247,7 +247,7 @@ func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, so
} }
if field != "" && value != "" { if field != "" && value != "" {
session = session.And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%") session = session.And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%")
} }
if sortField == "" || sortOrder == "" { if sortField == "" || sortOrder == "" {

View File

@ -19,7 +19,6 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/beego/beego"
"github.com/beego/beego/context" "github.com/beego/beego/context"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/pquerna/otp" "github.com/pquerna/otp"
@ -39,10 +38,11 @@ type TotpMfa struct {
} }
func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) { func (mfa *TotpMfa) Initiate(ctx *context.Context, userId string) (*MfaProps, error) {
issuer := beego.AppConfig.String("appname") //issuer := beego.AppConfig.String("appname")
if issuer == "" { //if issuer == "" {
issuer = "casdoor" // issuer = "casdoor"
} //}
issuer := "Casdoor"
key, err := totp.Generate(totp.GenerateOpts{ key, err := totp.Generate(totp.GenerateOpts{
Issuer: issuer, Issuer: issuer,

View File

@ -16,7 +16,6 @@ package object
import ( import (
"fmt" "fmt"
"net/http"
"github.com/casdoor/casdoor/pp" "github.com/casdoor/casdoor/pp"
@ -153,7 +152,7 @@ func DeletePayment(payment *Payment) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func notifyPayment(request *http.Request, body []byte, owner string, paymentName string) (*Payment, *pp.NotifyResult, error) { func notifyPayment(body []byte, owner string, paymentName string) (*Payment, *pp.NotifyResult, error) {
payment, err := getPayment(owner, paymentName) payment, err := getPayment(owner, paymentName)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -167,7 +166,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
pProvider, cert, err := provider.getPaymentProvider() pProvider, err := GetPaymentProvider(provider)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -181,7 +180,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
return nil, nil, err return nil, nil, err
} }
notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, payment.OutOrderId) notifyResult, err := pProvider.Notify(body, payment.OutOrderId)
if err != nil { if err != nil {
return payment, nil, err return payment, nil, err
} }
@ -202,8 +201,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
return payment, notifyResult, nil return payment, notifyResult, nil
} }
func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string) (*Payment, error) { func NotifyPayment(body []byte, owner string, paymentName string) (*Payment, error) {
payment, notifyResult, err := notifyPayment(request, body, owner, paymentName) payment, notifyResult, err := notifyPayment(body, owner, paymentName)
if payment != nil { if payment != nil {
if err != nil { if err != nil {
payment.State = pp.PaymentStateError payment.State = pp.PaymentStateError
@ -231,7 +230,7 @@ func invoicePayment(payment *Payment) (string, error) {
return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider) return "", fmt.Errorf("the payment provider: %s does not exist", payment.Provider)
} }
pProvider, _, err := provider.getPaymentProvider() pProvider, err := GetPaymentProvider(provider)
if err != nil { if err != nil {
return "", err return "", err
} }

View File

@ -61,10 +61,6 @@ type PermissionRule struct {
const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field const builtInAvailableField = 5 // Casdoor built-in adapter, use V5 to filter permission, so has 5 available field
func (p *Permission) GetId() string {
return util.GetId(p.Owner, p.Name)
}
func GetPermissionCount(owner, field, value string) (int64, error) { func GetPermissionCount(owner, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "") session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Permission{}) return session.Count(&Permission{})
@ -262,9 +258,59 @@ func DeletePermission(permission *Permission) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error) { func getPermissionsByUser(userId string) ([]*Permission, error) {
permissions := []*Permission{} permissions := []*Permission{}
err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&permissions) err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
res := []*Permission{}
for _, permission := range permissions {
if util.InSlice(permission.Users, userId) {
res = append(res, permission)
}
}
return res, nil
}
func GetPermissionsByRole(roleId string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Where("roles like ?", "%"+roleId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
res := []*Permission{}
for _, permission := range permissions {
if util.InSlice(permission.Roles, roleId) {
res = append(res, permission)
}
}
return res, nil
}
func GetPermissionsByResource(resourceId string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Where("resources like ?", "%"+resourceId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
res := []*Permission{}
for _, permission := range permissions {
if util.InSlice(permission.Resources, resourceId) {
res = append(res, permission)
}
}
return res, nil
}
func getPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error) {
permissions, err := getPermissionsByUser(userId)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -281,14 +327,13 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
permFromRoles := []*Permission{} permFromRoles := []*Permission{}
roles, err := GetRolesByUser(userId) roles, err := getRolesByUser(userId)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
for _, role := range roles { for _, role := range roles {
perms := []*Permission{} perms, err := GetPermissionsByRole(role.GetId())
err := ormer.Engine.Where("roles like ?", "%"+role.GetId()+"\"%").Find(&perms)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -306,26 +351,6 @@ func GetPermissionsAndRolesByUser(userId string) ([]*Permission, []*Role, error)
return permissions, roles, nil return permissions, roles, nil
} }
func GetPermissionsByRole(roleId string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Where("roles like ?", "%"+roleId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
return permissions, nil
}
func GetPermissionsByResource(resourceId string) ([]*Permission, error) {
permissions := []*Permission{}
err := ormer.Engine.Where("resources like ?", "%"+resourceId+"\"%").Find(&permissions)
if err != nil {
return permissions, err
}
return permissions, nil
}
func GetPermissionsBySubmitter(owner string, submitter string) ([]*Permission, error) { func GetPermissionsBySubmitter(owner string, submitter string) ([]*Permission, error) {
permissions := []*Permission{} permissions := []*Permission{}
err := ormer.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Submitter: submitter}) err := ormer.Engine.Desc("created_time").Find(&permissions, &Permission{Owner: owner, Submitter: submitter})
@ -346,20 +371,6 @@ func GetPermissionsByModel(owner string, model string) ([]*Permission, error) {
return permissions, nil return permissions, nil
} }
func ContainsAsterisk(userId string, users []string) bool {
containsAsterisk := false
group, _ := util.GetOwnerAndNameFromId(userId)
for _, user := range users {
permissionGroup, permissionUserName := util.GetOwnerAndNameFromId(user)
if permissionGroup == group && permissionUserName == "*" {
containsAsterisk = true
break
}
}
return containsAsterisk
}
func GetMaskedPermissions(permissions []*Permission) []*Permission { func GetMaskedPermissions(permissions []*Permission) []*Permission {
for _, permission := range permissions { for _, permission := range permissions {
permission.Users = nil permission.Users = nil
@ -389,3 +400,27 @@ func GroupPermissionsByModelAdapter(permissions []*Permission) map[string][]stri
return m return m
} }
func (p *Permission) GetId() string {
return util.GetId(p.Owner, p.Name)
}
func (p *Permission) isUserHit(name string) bool {
targetOrg, _ := util.GetOwnerAndNameFromId(name)
for _, user := range p.Users {
userOrg, userName := util.GetOwnerAndNameFromId(user)
if userOrg == targetOrg && userName == "*" {
return true
}
}
return false
}
func (p *Permission) isResourceHit(name string) bool {
for _, resource := range p.Resources {
if name == resource {
return true
}
}
return false
}

View File

@ -258,7 +258,7 @@ func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionI
} }
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string { func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) []string {
permissions, _, err := GetPermissionsAndRolesByUser(userId) permissions, _, err := getPermissionsAndRolesByUser(userId)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -293,7 +293,7 @@ func GetAllActions(userId string) []string {
} }
func GetAllRoles(userId string) []string { func GetAllRoles(userId string) []string {
roles, err := GetRolesByUser(userId) roles, err := getRolesByUser(userId)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@ -172,7 +172,7 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
return nil, err return nil, err
} }
pProvider, _, err := provider.getPaymentProvider() pProvider, err := GetPaymentProvider(provider)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -17,31 +17,24 @@
package object package object
import ( //func TestProduct(t *testing.T) {
"testing" // InitConfig()
//
"github.com/casdoor/casdoor/pp" // product, _ := GetProduct("admin/product_123")
"github.com/casdoor/casdoor/util" // provider, _ := getProvider(product.Owner, "provider_pay_alipay")
) // cert, _ := getCert(product.Owner, "cert-pay-alipay")
// pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2)
func TestProduct(t *testing.T) { // if err != nil {
InitConfig() // panic(err)
// }
product, _ := GetProduct("admin/product_123") //
provider, _ := getProvider(product.Owner, "provider_pay_alipay") // paymentName := util.GenerateTimeId()
cert, _ := getCert(product.Owner, "cert-pay-alipay") // returnUrl := ""
pProvider, err := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, provider.ClientId2) // notifyUrl := ""
if err != nil { // payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
panic(err) // if err != nil {
} // panic(err)
// }
paymentName := util.GenerateTimeId() //
returnUrl := "" // println(payUrl)
notifyUrl := "" //}
payUrl, _, err := pProvider.Pay(provider.Name, product.Name, "alice", paymentName, product.DisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
if err != nil {
panic(err)
}
println(payUrl)
}

View File

@ -251,30 +251,69 @@ func DeleteProvider(provider *Provider) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) { func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) {
cert := &Cert{} cert := &Cert{}
if p.Cert != "" { if p.Cert != "" {
var err error var err error
cert, err = getCert(p.Owner, p.Cert) cert, err = GetCert(util.GetId(p.Owner, p.Cert))
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
if cert == nil { if cert == nil {
return nil, nil, fmt.Errorf("the cert: %s does not exist", p.Cert) return nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
} }
} }
typ := p.Type
pProvider, err := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey, p.ClientId2) if typ == "Dummy" {
if err != nil { pp, err := pp.NewDummyPaymentProvider()
return nil, cert, err if err != nil {
return nil, err
}
return pp, nil
} else if typ == "Alipay" {
if p.Metadata != "" {
// alipay provider store rootCert's name in metadata
rootCert, err := GetCert(util.GetId(p.Owner, p.Metadata))
if err != nil {
return nil, err
}
if rootCert == nil {
return nil, fmt.Errorf("the cert: %s does not exist", p.Metadata)
}
pp, err := pp.NewAlipayPaymentProvider(p.ClientId, cert.Certificate, cert.PrivateKey, rootCert.Certificate, rootCert.PrivateKey)
if err != nil {
return nil, err
}
return pp, nil
} else {
return nil, fmt.Errorf("the metadata of alipay provider is empty")
}
} else if typ == "GC" {
return pp.NewGcPaymentProvider(p.ClientId, p.ClientSecret, p.Host), nil
} else if typ == "WeChat Pay" {
pp, err := pp.NewWechatPaymentProvider(p.ClientId, p.ClientSecret, p.ClientId2, cert.Certificate, cert.PrivateKey)
if err != nil {
return nil, err
}
return pp, nil
} else if typ == "PayPal" {
pp, err := pp.NewPaypalPaymentProvider(p.ClientId, p.ClientSecret)
if err != nil {
return nil, err
}
return pp, nil
} else if typ == "Stripe" {
pp, err := pp.NewStripePaymentProvider(p.ClientId, p.ClientSecret)
if err != nil {
return nil, err
}
return pp, nil
} else {
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
} }
if pProvider == nil { return nil, nil
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
}
return pProvider, cert, nil
} }
func (p *Provider) GetId() string { func (p *Provider) GetId() string {
@ -376,6 +415,8 @@ func FromProviderToIdpInfo(ctx *context.Context, provider *Provider) *idp.Provid
providerInfo.ClientId = provider.ClientId2 providerInfo.ClientId = provider.ClientId2
providerInfo.ClientSecret = provider.ClientSecret2 providerInfo.ClientSecret = provider.ClientSecret2
} }
} else if provider.Type == "AzureAD" {
providerInfo.HostUrl = provider.Domain
} }
return providerInfo return providerInfo

View File

@ -32,6 +32,7 @@ type Role struct {
Description string `xorm:"varchar(100)" json:"description"` Description string `xorm:"varchar(100)" json:"description"`
Users []string `xorm:"mediumtext" json:"users"` Users []string `xorm:"mediumtext" json:"users"`
Groups []string `xorm:"mediumtext" json:"groups"`
Roles []string `xorm:"mediumtext" json:"roles"` Roles []string `xorm:"mediumtext" json:"roles"`
Domains []string `xorm:"mediumtext" json:"domains"` Domains []string `xorm:"mediumtext" json:"domains"`
IsEnabled bool `json:"isEnabled"` IsEnabled bool `json:"isEnabled"`
@ -252,15 +253,30 @@ func (role *Role) GetId() string {
return fmt.Sprintf("%s/%s", role.Owner, role.Name) return fmt.Sprintf("%s/%s", role.Owner, role.Name)
} }
func GetRolesByUser(userId string) ([]*Role, error) { func getRolesByUserInternal(userId string) ([]*Role, error) {
roles := []*Role{} roles := []*Role{}
err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&roles) err := ormer.Engine.Where("users like ?", "%"+userId+"\"%").Find(&roles)
if err != nil { if err != nil {
return roles, err return roles, err
} }
allRolesIds := make([]string, 0, len(roles)) res := []*Role{}
for _, role := range roles {
if util.InSlice(role.Users, userId) {
res = append(res, role)
}
}
return res, nil
}
func getRolesByUser(userId string) ([]*Role, error) {
roles, err := getRolesByUserInternal(userId)
if err != nil {
return roles, err
}
allRolesIds := []string{}
for _, role := range roles { for _, role := range roles {
allRolesIds = append(allRolesIds, role.GetId()) allRolesIds = append(allRolesIds, role.GetId())
} }
@ -336,16 +352,6 @@ func GetMaskedRoles(roles []*Role) []*Role {
return roles return roles
} }
func GetRolesByNamePrefix(owner string, prefix string) ([]*Role, error) {
roles := []*Role{}
err := ormer.Engine.Where("owner=? and name like ?", owner, prefix+"%").Find(&roles)
if err != nil {
return roles, err
}
return roles, nil
}
// GetAncestorRoles returns a list of roles that contain the given roleIds // GetAncestorRoles returns a list of roles that contain the given roleIds
func GetAncestorRoles(roleIds ...string) ([]*Role, error) { func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
var ( var (

View File

@ -72,7 +72,7 @@ func GetTruncatedPath(provider *Provider, fullFilePath string, limit int) string
} }
func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) { func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
escapedPath := util.UrlJoin(provider.PathPrefix, escapePath(fullFilePath)) escapedPath := util.UrlJoin(provider.PathPrefix, fullFilePath)
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath) objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
host := "" host := ""

View File

@ -15,6 +15,7 @@
package object package object
import ( import (
"database/sql"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -31,7 +32,7 @@ type Credential struct {
} }
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) { func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
var results []map[string]string var results []map[string]sql.NullString
err := syncer.Ormer.Engine.Table(syncer.getTable()).Find(&results) err := syncer.Ormer.Engine.Table(syncer.getTable()).Find(&results)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -15,6 +15,7 @@
package object package object
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
@ -196,7 +197,7 @@ func (syncer *Syncer) getUserValue(user *User, key string) string {
} }
} }
func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*OriginalUser { func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]sql.NullString) []*OriginalUser {
users := []*OriginalUser{} users := []*OriginalUser{}
for _, result := range results { for _, result := range results {
originalUser := &OriginalUser{ originalUser := &OriginalUser{
@ -216,11 +217,11 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
names := strings.Split(tableColumnName, "+") names := strings.Split(tableColumnName, "+")
var values []string var values []string
for _, name := range names { for _, name := range names {
values = append(values, result[strings.Trim(name, " ")]) values = append(values, result[strings.Trim(name, " ")].String)
} }
value = strings.Join(values, " ") value = strings.Join(values, " ")
} else { } else {
value = result[tableColumnName] value = result[tableColumnName].String
} }
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value) syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
} }
@ -249,9 +250,9 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
// enable // enable
value, ok := result["ENABLED"] value, ok := result["ENABLED"]
if ok { if ok {
originalUser.IsForbidden = !util.ParseBool(value) originalUser.IsForbidden = !util.ParseBool(value.String)
} else { } else {
originalUser.IsForbidden = !util.ParseBool(result["enabled"]) originalUser.IsForbidden = !util.ParseBool(result["enabled"].String)
} }
} }

View File

@ -209,17 +209,22 @@ func GenerateCasToken(userId string, service string) (string, error) {
return "", err return "", err
} }
tmp := map[string]string{} tmp := map[string]interface{}{}
err = json.Unmarshal(data, &tmp) err = json.Unmarshal(data, &tmp)
if err != nil { if err != nil {
return "", err return "", err
} }
for k, v := range tmp { for k, v := range tmp {
if v != "" { value := fmt.Sprintf("%v", v)
if value == "<nil>" || value == "[]" || value == "map[]" {
value = ""
}
if value != "" {
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{ authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k, Name: k,
Value: v, Value: value,
}) })
} }
} }

View File

@ -16,6 +16,7 @@ package object
import ( import (
"fmt" "fmt"
"strconv"
"strings" "strings"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
@ -483,7 +484,7 @@ func GetMaskedUsers(users []*User, errs ...error) ([]*User, error) {
return users, nil return users, nil
} }
func GetLastUser(owner string) (*User, error) { func getLastUser(owner string) (*User, error) {
user := User{Owner: owner} user := User{Owner: owner}
existed, err := ormer.Engine.Desc("created_time", "id").Get(&user) existed, err := ormer.Engine.Desc("created_time", "id").Get(&user)
if err != nil { if err != nil {
@ -614,9 +615,18 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
} }
func AddUser(user *User) (bool, error) { func AddUser(user *User) (bool, error) {
var err error
if user.Id == "" { if user.Id == "" {
user.Id = util.GenerateId() application, err := GetApplicationByUser(user)
if err != nil {
return false, err
}
id, err := GenerateIdForNewUser(application)
if err != nil {
return false, err
}
user.Id = id
} }
if user.Owner == "" || user.Name == "" { if user.Owner == "" || user.Name == "" {
@ -632,7 +642,7 @@ func AddUser(user *User) (bool, error) {
user.UpdateUserPassword(organization) user.UpdateUserPassword(organization)
} }
err = user.UpdateUserHash() err := user.UpdateUserHash()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -786,7 +796,7 @@ func ExtendUserWithRolesAndPermissions(user *User) (err error) {
return return
} }
user.Permissions, user.Roles, err = GetPermissionsAndRolesByUser(user.GetId()) user.Permissions, user.Roles, err = getPermissionsAndRolesByUser(user.GetId())
if err != nil { if err != nil {
return err return err
} }
@ -900,3 +910,22 @@ func (user *User) IsGlobalAdmin() bool {
return user.Owner == "built-in" return user.Owner == "built-in"
} }
func GenerateIdForNewUser(application *Application) (string, error) {
if application.GetSignupItemRule("ID") != "Incremental" {
return util.GenerateId(), nil
}
lastUser, err := getLastUser(application.Organization)
if err != nil {
return "", err
}
lastUserId := -1
if lastUser != nil {
lastUserId = util.ParseInt(lastUser.Id)
}
res := strconv.Itoa(lastUserId + 1)
return res, nil
}

View File

@ -35,7 +35,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error()) fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") { if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "did not properly respond after a period of time") {
return nil, "", nil return nil, "", nil
} else { } else {
return nil, "", err return nil, "", err

View File

@ -87,7 +87,7 @@ func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) {
users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group)) users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group))
if err != nil { if err != nil {
if err == errors.ERR_NAME_NOT_FOUND { if err == errors.ErrNameNotFound {
return []string{}, nil return []string{}, nil
} }
return nil, err return nil, err

View File

@ -320,6 +320,11 @@ func CheckPermissionForUpdateUser(oldUser, newUser *User, isAdmin bool, lang str
itemsChanged = append(itemsChanged, item) itemsChanged = append(itemsChanged, item)
} }
if oldUser.Score != newUser.Score {
item := GetAccountItemByName("Score", organization)
itemsChanged = append(itemsChanged, item)
}
for i := range itemsChanged { for i := range itemsChanged {
if pass, err := CheckAccountItemModifyRule(itemsChanged[i], isAdmin, lang); !pass { if pass, err := CheckAccountItemModifyRule(itemsChanged[i], isAdmin, lang); !pass {
return pass, err return pass, err

View File

@ -16,9 +16,9 @@ package pp
import ( import (
"context" "context"
"net/http" "encoding/json"
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay" "github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay" "github.com/go-pay/gopay/alipay"
) )
@ -28,6 +28,11 @@ type AlipayPaymentProvider struct {
} }
func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) (*AlipayPaymentProvider, error) { func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) (*AlipayPaymentProvider, error) {
// clientId => appId
// cert.Certificate => appCertificate
// cert.PrivateKey => appPrivateKey
// rootCert.Certificate => authorityPublicKey
// rootCert.PrivateKey => authorityRootPublicKey
pp := &AlipayPaymentProvider{} pp := &AlipayPaymentProvider{}
client, err := alipay.NewClient(appId, appPrivateKey, true) client, err := alipay.NewClient(appId, appPrivateKey, true)
@ -46,54 +51,60 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
// pp.Client.DebugSwitch = gopay.DebugOn // pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{} bm := gopay.BodyMap{}
pp.Client.SetReturnUrl(returnUrl)
bm.Set("providerName", providerName) pp.Client.SetNotifyUrl(notifyUrl)
bm.Set("productName", productName) bm.Set("subject", joinAttachString([]string{productName, productDisplayName, providerName}))
bm.Set("return_url", returnUrl)
bm.Set("notify_url", notifyUrl)
bm.Set("subject", productDisplayName)
bm.Set("out_trade_no", paymentName) bm.Set("out_trade_no", paymentName)
bm.Set("total_amount", getPriceString(price)) bm.Set("total_amount", priceFloat64ToString(price))
payUrl, err := pp.Client.TradePagePay(context.Background(), bm) payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
return payUrl, "", nil return payUrl, paymentName, nil
} }
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
bm, err := alipay.ParseNotifyToBodyMap(request) bm := gopay.BodyMap{}
bm.Set("out_trade_no", orderId)
aliRsp, err := pp.Client.TradeQuery(context.Background(), bm)
notifyResult := &NotifyResult{}
if err != nil { if err != nil {
errRsp := &alipay.ErrorResponse{}
unmarshalErr := json.Unmarshal([]byte(err.Error()), errRsp)
if unmarshalErr != nil {
return nil, err
}
if errRsp.SubCode == "ACQ.TRADE_NOT_EXIST" {
notifyResult.PaymentStatus = PaymentStateCanceled
return notifyResult, nil
}
return nil, err return nil, err
} }
switch aliRsp.Response.TradeStatus {
providerName := bm.Get("providerName") case "WAIT_BUYER_PAY":
productName := bm.Get("productName") notifyResult.PaymentStatus = PaymentStateCreated
return notifyResult, nil
productDisplayName := bm.Get("subject") case "TRADE_CLOSED":
paymentName := bm.Get("out_trade_no") notifyResult.PaymentStatus = PaymentStateTimeout
price := util.ParseFloat(bm.Get("total_amount")) return notifyResult, nil
case "TRADE_SUCCESS":
ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm) // skip
if err != nil { default:
return nil, err notifyResult.PaymentStatus = PaymentStateError
notifyResult.NotifyMessage = fmt.Sprintf("unexpected alipay trade state: %v", aliRsp.Response.TradeStatus)
return notifyResult, nil
} }
if !ok { productDisplayName, productName, providerName, _ := parseAttachString(aliRsp.Response.Subject)
return nil, err notifyResult = &NotifyResult{
}
notifyResult := &NotifyResult{
ProductName: productName, ProductName: productName,
ProductDisplayName: productDisplayName, ProductDisplayName: productDisplayName,
ProviderName: providerName, ProviderName: providerName,
OrderId: orderId, OrderId: orderId,
PaymentStatus: PaymentStatePaid, PaymentStatus: PaymentStatePaid,
Price: price, Price: priceStringToFloat64(aliRsp.Response.TotalAmount),
PaymentName: paymentName, PaymentName: orderId,
} }
return notifyResult, nil return notifyResult, nil
} }

View File

@ -14,8 +14,6 @@
package pp package pp
import "net/http"
type DummyPaymentProvider struct{} type DummyPaymentProvider struct{}
func NewDummyPaymentProvider() (*DummyPaymentProvider, error) { func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
@ -27,7 +25,7 @@ func (pp *DummyPaymentProvider) Pay(providerName string, productName string, pay
return returnUrl, "", nil return returnUrl, "", nil
} }
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
return &NotifyResult{ return &NotifyResult{
PaymentStatus: PaymentStatePaid, PaymentStatus: PaymentStatePaid,
}, nil }, nil

View File

@ -216,7 +216,7 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
return payRespInfo.PayUrl, "", nil return payRespInfo.PayUrl, "", nil
} }
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
reqBody := GcRequestBody{} reqBody := GcRequestBody{}
m, err := url.ParseQuery(string(body)) m, err := url.ParseQuery(string(body))
if err != nil { if err != nil {

View File

@ -18,7 +18,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/http"
"strconv" "strconv"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
@ -88,7 +87,7 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
} }
func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { func (pp *PaypalPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
notifyResult := &NotifyResult{} notifyResult := &NotifyResult{}
captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil) captureRsp, err := pp.Client.OrderCapture(context.Background(), orderId, nil)
if err != nil { if err != nil {

View File

@ -14,8 +14,6 @@
package pp package pp
import "net/http"
type PaymentState string type PaymentState string
const ( const (
@ -42,45 +40,7 @@ type NotifyResult struct {
type PaymentProvider interface { type PaymentProvider interface {
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error)
Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) Notify(body []byte, orderId string) (*NotifyResult, error)
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
GetResponseError(err error) string GetResponseError(err error) string
} }
func GetPaymentProvider(typ string, clientId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string, clientId2 string) (PaymentProvider, error) {
if typ == "Dummy" {
pp, err := NewDummyPaymentProvider()
if err != nil {
return nil, err
}
return pp, nil
} else if typ == "Alipay" {
pp, err := NewAlipayPaymentProvider(clientId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
if err != nil {
return nil, err
}
return pp, nil
} else if typ == "GC" {
return NewGcPaymentProvider(clientId, clientSecret, host), nil
} else if typ == "WeChat Pay" {
pp, err := NewWechatPaymentProvider(clientId, clientSecret, clientId2, appCertificate, appPrivateKey)
if err != nil {
return nil, err
}
return pp, nil
} else if typ == "PayPal" {
pp, err := NewPaypalPaymentProvider(clientId, clientSecret)
if err != nil {
return nil, err
}
return pp, nil
} else if typ == "Stripe" {
pp, err := NewStripePaymentProvider(clientId, clientSecret)
if err != nil {
return nil, err
}
return pp, nil
}
return nil, nil
}

View File

@ -16,7 +16,6 @@ package pp
import ( import (
"fmt" "fmt"
"net/http"
"time" "time"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
@ -94,7 +93,7 @@ func (pp *StripePaymentProvider) Pay(providerName string, productName string, pa
return sCheckout.URL, sCheckout.ID, nil return sCheckout.URL, sCheckout.ID, nil
} }
func (pp *StripePaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
notifyResult := &NotifyResult{} notifyResult := &NotifyResult{}
sCheckout, err := stripeCheckout.Get(orderId, nil) sCheckout, err := stripeCheckout.Get(orderId, nil)
if err != nil { if err != nil {

View File

@ -49,3 +49,11 @@ func priceFloat64ToInt64(price float64) int64 {
func priceFloat64ToString(price float64) string { func priceFloat64ToString(price float64) string {
return strconv.FormatFloat(price, 'f', 2, 64) return strconv.FormatFloat(price, 'f', 2, 64)
} }
func priceStringToFloat64(price string) float64 {
f, err := strconv.ParseFloat(price, 64)
if err != nil {
panic(err)
}
return f
}

View File

@ -18,7 +18,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/http"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay" "github.com/go-pay/gopay"
@ -87,7 +86,7 @@ func (pp *WechatPaymentProvider) Pay(providerName string, productName string, pa
return nativeRsp.Response.CodeUrl, paymentName, nil // Wechat can use paymentName as the OutTradeNo to query order status return nativeRsp.Response.CodeUrl, paymentName, nil // Wechat can use paymentName as the OutTradeNo to query order status
} }
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { func (pp *WechatPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
notifyResult := &NotifyResult{} notifyResult := &NotifyResult{}
queryRsp, err := pp.Client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, orderId) queryRsp, err := pp.Client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, orderId)
if err != nil { if err != nil {

67
radius/server.go Normal file
View File

@ -0,0 +1,67 @@
// Copyright 2023 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 radius
import (
"log"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
)
// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a#tab_3
func StartRadiusServer() {
server := radius.PacketServer{
Addr: "0.0.0.0:" + conf.GetConfigString("radiusServerPort"),
Handler: radius.HandlerFunc(handlerRadius),
SecretSource: radius.StaticSecretSource([]byte(`secret`)),
}
log.Printf("Starting Radius server on %s", server.Addr)
if err := server.ListenAndServe(); err != nil {
log.Fatalf("StartRadiusServer() failed, err = %v", err)
}
}
func handlerRadius(w radius.ResponseWriter, r *radius.Request) {
switch r.Code {
case radius.CodeAccessRequest:
handleAccessRequest(w, r)
default:
log.Printf("radius message, code = %d", r.Code)
}
}
func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
username := rfc2865.UserName_GetString(r.Packet)
password := rfc2865.UserPassword_GetString(r.Packet)
organization := parseOrganization(r.Packet)
code := radius.CodeAccessAccept
log.Printf("username=%v, password=%v, code=%v, org=%v", username, password, code, organization)
if organization == "" {
code = radius.CodeAccessReject
w.Write(r.Response(code))
return
}
_, msg := object.CheckUserPassword(organization, username, password, "en")
if msg != "" {
code = radius.CodeAccessReject
w.Write(r.Response(code))
return
}
w.Write(r.Response(code))
}

62
radius/server_test.go Normal file
View File

@ -0,0 +1,62 @@
// Copyright 2023 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.
//go:build !skipCi
// +build !skipCi
package radius
import (
"context"
"testing"
"layeh.com/radius"
"layeh.com/radius/rfc2865"
)
func TestAccessRequestRejected(t *testing.T) {
packet := radius.New(radius.CodeAccessRequest, []byte(`secret`))
rfc2865.UserName_SetString(packet, "admin")
rfc2865.UserPassword_SetString(packet, "12345")
vsa, err := radius.NewVendorSpecific(OrganizationVendorID, []byte("built-in"))
if err != nil {
t.Fatal(err)
}
packet.Add(rfc2865.VendorSpecific_Type, vsa)
response, err := radius.Exchange(context.Background(), packet, "localhost:1812")
if err != nil {
t.Fatal(err)
}
if response.Code != radius.CodeAccessReject {
t.Fatalf("Expected %v, got %v", radius.CodeAccessReject, response.Code)
}
}
func TestAccessRequestAccepted(t *testing.T) {
packet := radius.New(radius.CodeAccessRequest, []byte(`secret`))
rfc2865.UserName_SetString(packet, "admin")
rfc2865.UserPassword_SetString(packet, "123")
vsa, err := radius.NewVendorSpecific(OrganizationVendorID, []byte("built-in"))
if err != nil {
t.Fatal(err)
}
packet.Add(rfc2865.VendorSpecific_Type, vsa)
response, err := radius.Exchange(context.Background(), packet, "localhost:1812")
if err != nil {
t.Fatal(err)
}
if response.Code != radius.CodeAccessAccept {
t.Fatalf("Expected %v, got %v", radius.CodeAccessAccept, response.Code)
}
}

40
radius/util.go Normal file
View File

@ -0,0 +1,40 @@
// Copyright 2023 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 radius
import (
"layeh.com/radius"
"layeh.com/radius/rfc2865"
)
const (
OrganizationVendorID = uint32(100)
)
func parseOrganization(p *radius.Packet) string {
for _, avp := range p.Attributes {
if avp.Type == rfc2865.VendorSpecific_Type {
attr := avp.Attribute
vendorId, value, err := radius.VendorSpecific(attr)
if err != nil {
return ""
}
if vendorId == OrganizationVendorID {
return string(value)
}
}
}
return ""
}

View File

@ -40,6 +40,13 @@ func CorsFilter(ctx *context.Context) {
return return
} }
if ctx.Request.RequestURI == "/api/userinfo" {
ctx.Output.Header(headerAllowOrigin, origin)
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
return
}
if origin != "" && originConf != "" && origin != originConf { if origin != "" && originConf != "" && origin != originConf {
ok, err := object.IsOriginAllowed(origin) ok, err := object.IsOriginAllowed(origin)
if err != nil { if err != nil {

View File

@ -63,6 +63,7 @@ func initAPI() {
beego.Router("/api/webhook", &controllers.ApiController{}, "POST:HandleOfficialAccountEvent") beego.Router("/api/webhook", &controllers.ApiController{}, "POST:HandleOfficialAccountEvent")
beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType") beego.Router("/api/get-webhook-event", &controllers.ApiController{}, "GET:GetWebhookEventType")
beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus") beego.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
beego.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations") beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization") beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
@ -247,10 +248,10 @@ func initAPI() {
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms") beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
beego.Router("/api/send-notification", &controllers.ApiController{}, "POST:SendNotification") beego.Router("/api/send-notification", &controllers.ApiController{}, "POST:SendNotification")
beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "Get:WebAuthnSignupBegin") beego.Router("/api/webauthn/signup/begin", &controllers.ApiController{}, "GET:WebAuthnSignupBegin")
beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "Post:WebAuthnSignupFinish") beego.Router("/api/webauthn/signup/finish", &controllers.ApiController{}, "POST:WebAuthnSignupFinish")
beego.Router("/api/webauthn/signin/begin", &controllers.ApiController{}, "Get:WebAuthnSigninBegin") beego.Router("/api/webauthn/signin/begin", &controllers.ApiController{}, "GET:WebAuthnSigninBegin")
beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "Post:WebAuthnSigninFinish") beego.Router("/api/webauthn/signin/finish", &controllers.ApiController{}, "POST:WebAuthnSigninFinish")
beego.Router("/api/mfa/setup/initiate", &controllers.ApiController{}, "POST:MfaSetupInitiate") beego.Router("/api/mfa/setup/initiate", &controllers.ApiController{}, "POST:MfaSetupInitiate")
beego.Router("/api/mfa/setup/verify", &controllers.ApiController{}, "POST:MfaSetupVerify") beego.Router("/api/mfa/setup/verify", &controllers.ApiController{}, "POST:MfaSetupVerify")

View File

@ -300,3 +300,12 @@ func GetValueFromDataSourceName(key string, dataSourceName string) string {
return "" return ""
} }
func GetUsernameFromEmail(email string) string {
tokens := strings.Split(email, "@")
if len(tokens) == 0 {
return uuid.NewString()
} else {
return tokens[0]
}
}

View File

@ -227,7 +227,7 @@ class AdapterEditPage extends React.Component {
</Row> </Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:DB Test"), i18next.t("provider:DB Test - Tooltip"))} : {Setting.getLabel(i18next.t("provider:DB test"), i18next.t("provider:DB test - Tooltip"))} :
</Col> </Col>
<Col span={2} > <Col span={2} >
<Button type={"primary"} onClick={() => { <Button type={"primary"} onClick={() => {

View File

@ -129,6 +129,13 @@ class AdapterListPage extends BaseListPage {
{text: "Database", value: "Database"}, {text: "Database", value: "Database"},
], ],
}, },
{
title: i18next.t("syncer:Database type"),
dataIndex: "databaseType",
key: "databaseType",
width: "120px",
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
},
{ {
title: i18next.t("provider:Host"), title: i18next.t("provider:Host"),
dataIndex: "host", dataIndex: "host",
@ -144,6 +151,12 @@ class AdapterListPage extends BaseListPage {
width: "100px", width: "100px",
sorter: true, sorter: true,
...this.getColumnSearchProps("port"), ...this.getColumnSearchProps("port"),
render: (text, record, index) => {
if (text === 0) {
return "";
}
return text;
},
}, },
{ {
title: i18next.t("general:User"), title: i18next.t("general:User"),
@ -161,13 +174,6 @@ class AdapterListPage extends BaseListPage {
sorter: true, sorter: true,
...this.getColumnSearchProps("password"), ...this.getColumnSearchProps("password"),
}, },
{
title: i18next.t("syncer:Database type"),
dataIndex: "databaseType",
key: "databaseType",
width: "120px",
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
},
{ {
title: i18next.t("syncer:Database"), title: i18next.t("syncer:Database"),
dataIndex: "database", dataIndex: "database",

View File

@ -101,7 +101,7 @@ class PaymentResultPage extends React.Component {
payment: payment, payment: payment,
}); });
if (payment.state === "Created") { if (payment.state === "Created") {
if (["PayPal", "Stripe"].includes(payment.type)) { if (["PayPal", "Stripe", "Alipay"].includes(payment.type)) {
this.setState({ this.setState({
timeout: setTimeout(async() => { timeout: setTimeout(async() => {
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName); await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);

View File

@ -298,6 +298,13 @@ class PermissionListPage extends BaseListPage {
filterMultiple: false, filterMultiple: false,
width: "120px", width: "120px",
sorter: true, sorter: true,
render: (text, record, index) => {
return (
<Link to={`/users/${record.owner}/${encodeURIComponent(text)}`}>
{text}
</Link>
);
},
}, },
{ {
title: i18next.t("permission:Approver"), title: i18next.t("permission:Approver"),
@ -306,6 +313,13 @@ class PermissionListPage extends BaseListPage {
filterMultiple: false, filterMultiple: false,
width: "120px", width: "120px",
sorter: true, sorter: true,
render: (text, record, index) => {
return (
<Link to={`/users/${record.owner}/${encodeURIComponent(text)}`}>
{text}
</Link>
);
},
}, },
{ {
title: i18next.t("permission:Approve time"), title: i18next.t("permission:Approve time"),

View File

@ -1111,6 +1111,22 @@ class ProviderEditPage extends React.Component {
</Row> </Row>
) : null ) : null
} }
{
(this.state.provider.type === "Alipay") ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Root Cert"), i18next.t("general:Root Cert - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.metadata} onChange={(value => {this.updateProviderField("metadata", value);})}>
{
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
}
</Select>
</Col>
</Row>
) : null
}
{ {
this.state.provider.type === "Web3Onboard" ? ( this.state.provider.type === "Web3Onboard" ? (
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >

View File

@ -14,11 +14,12 @@
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row, Select, Switch} from "antd"; import {Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as RoleBackend from "./backend/RoleBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend"; import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as UserBackend from "./backend/UserBackend";
import * as GroupBackend from "./backend/GroupBackend";
import * as RoleBackend from "./backend/RoleBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import * as UserBackend from "./backend/UserBackend";
class RoleEditPage extends React.Component { class RoleEditPage extends React.Component {
constructor(props) { constructor(props) {
@ -30,6 +31,7 @@ class RoleEditPage extends React.Component {
role: null, role: null,
organizations: [], organizations: [],
users: [], users: [],
groups: [],
roles: [], roles: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit", mode: props.location.mode !== undefined ? props.location.mode : "edit",
}; };
@ -57,6 +59,7 @@ class RoleEditPage extends React.Component {
}); });
this.getUsers(this.state.organizationName); this.getUsers(this.state.organizationName);
this.getGroups(this.state.organizationName);
this.getRoles(this.state.organizationName); this.getRoles(this.state.organizationName);
}); });
} }
@ -84,6 +87,20 @@ class RoleEditPage extends React.Component {
}); });
} }
getGroups(organizationName) {
GroupBackend.getGroups(organizationName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
groups: res.data,
});
});
}
getRoles(organizationName) { getRoles(organizationName) {
RoleBackend.getRoles(organizationName) RoleBackend.getRoles(organizationName)
.then((res) => { .then((res) => {
@ -176,6 +193,17 @@ class RoleEditPage extends React.Component {
/> />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub groups"), i18next.t("role:Sub groups - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="multiple" style={{width: "100%"}} value={this.state.role.groups}
onChange={(value => {this.updateRoleField("groups", value);})}
options={this.state.groups.map((group) => Setting.getOption(`${group.owner}/${group.name}`, `${group.owner}/${group.name}`))}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} : {Setting.getLabel(i18next.t("role:Sub roles"), i18next.t("role:Sub roles - Tooltip"))} :

View File

@ -33,6 +33,7 @@ class RoleListPage extends BaseListPage {
createdTime: moment().format(), createdTime: moment().format(),
displayName: `New Role - ${randomName}`, displayName: `New Role - ${randomName}`,
users: [], users: [],
groups: [],
roles: [], roles: [],
domains: [], domains: [],
isEnabled: true, isEnabled: true,
@ -171,6 +172,17 @@ class RoleListPage extends BaseListPage {
return Setting.getTags(text, "users"); return Setting.getTags(text, "users");
}, },
}, },
{
title: i18next.t("role:Sub groups"),
dataIndex: "groups",
key: "groups",
// width: '100px',
sorter: true,
...this.getColumnSearchProps("groups"),
render: (text, record, index) => {
return Setting.getTags(text, "groups");
},
},
{ {
title: i18next.t("role:Sub roles"), title: i18next.t("role:Sub roles"),
dataIndex: "roles", dataIndex: "roles",

View File

@ -749,7 +749,7 @@ export function isMobile() {
} }
export function getFormattedDate(date) { export function getFormattedDate(date) {
if (date === undefined) { if (!date) {
return null; return null;
} }

View File

@ -379,7 +379,7 @@ export function getAuthUrl(application, provider, method) {
} }
let endpoint = authInfo[provider.type].endpoint; let endpoint = authInfo[provider.type].endpoint;
const redirectUri = `${window.location.origin}/callback`; let redirectUri = `${window.location.origin}/callback`;
const scope = authInfo[provider.type].scope; const scope = authInfo[provider.type].scope;
const isShortState = provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger"); const isShortState = provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger");
@ -390,6 +390,8 @@ export function getAuthUrl(application, provider, method) {
if (provider.domain !== "") { if (provider.domain !== "") {
endpoint = endpoint.replace("common", provider.domain); endpoint = endpoint.replace("common", provider.domain);
} }
} else if (provider.type === "Apple") {
redirectUri = `${window.location.origin}/api/callback`;
} }
if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook" if (provider.type === "Google" || provider.type === "GitHub" || provider.type === "QQ" || provider.type === "Facebook"
@ -448,7 +450,7 @@ export function getAuthUrl(application, provider, method) {
} 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") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&response_mode=form_post`; return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code%20id_token&scope=${scope}&response_mode=form_post`;
} else if (provider.type === "Steam") { } else if (provider.type === "Steam") {
return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`; return `${endpoint}?openid.claimed_id=http://specs.openid.net/auth/2.0/identifier_select&openid.identity=http://specs.openid.net/auth/2.0/identifier_select&openid.mode=checkid_setup&openid.ns=http://specs.openid.net/auth/2.0&openid.realm=${window.location.origin}&openid.return_to=${redirectUri}?state=${state}`;
} else if (provider.type === "Okta") { } else if (provider.type === "Okta") {