Compare commits

..

70 Commits

Author SHA1 Message Date
53ad454962 feat: responsive footer (#1003) 2022-08-10 20:31:42 +08:00
fb203a6f30 feat: delete .env to fix static file path bug (#999) 2022-08-10 12:22:27 +08:00
f716a0985f Add disableSsl to provider. 2022-08-09 23:38:35 +08:00
340fbe135d Fix error in wrapActionResponse() 2022-08-09 23:34:07 +08:00
79119760f2 style: golint (#988) 2022-08-09 16:50:49 +08:00
4dd67a8dcb fix: fix all frontend warnings (#983)
* fix:fix all frontend warnings

* fix:fix all frontend warnings

* fix:fix all frontend warnings

* fix:fix all frontend warnings

* fix:fix all frontend warnings

* fix:fix all frontend warnings
2022-08-09 12:19:56 +08:00
deed857788 chore(style): allow case declarations and ban var (#987)
* chore(style): allow case declarations

* chore(style): ban `var` and prefer `const`
2022-08-08 23:35:24 +08:00
802995ed16 refactor: remove WeChat unionId to properties (#985) 2022-08-08 18:43:12 +08:00
b14554a5ba feat(web): check style when commit (#980)
feat(web): check style when commit
2022-08-08 00:10:31 +08:00
4665ffa759 Update i18n data 2022-08-08 00:02:47 +08:00
f914e8e929 Add permission_enforcer.go 2022-08-07 23:57:06 +08:00
dc33b41107 feat: expose some casbin APIs (#955)
* feat: expose some casbin APIs

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

* feat: add BatchEnforce API

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

* fix: solve requested changes

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-08-07 23:42:45 +08:00
ee8dd23a56 fix: fixed footer css (#951) 2022-08-07 17:22:52 +08:00
08d0269e30 refactor: New Crowdin translations by Github Action (#974)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-08-07 16:06:52 +08:00
8e5cd18c91 fix: Restrict the request permissions of providers and applications (#970) 2022-08-07 16:05:05 +08:00
32b4d98c2a Add ExtendProductWithProviders(). 2022-08-07 15:45:06 +08:00
2ea58cd639 chore(style): use gofumpt to fmt go code (#967) 2022-08-07 12:26:14 +08:00
45d2745b67 chore(style): add eslint rules: no-unused-imports and no-unused-vars (#976)
* feat(web): no-unused-imports and no-unused-vars

* chore: fix json style
2022-08-07 11:51:53 +08:00
cba338eef2 Merge pull request #973 from qianxi0410/eslint
feat(web): add some eslint rules
2022-08-07 00:41:51 +08:00
c428de6e42 feat: fix some comma dangle 2022-08-07 00:17:27 +08:00
9bca6bb72e feat: no-multi-spacing 2022-08-07 00:06:20 +08:00
cd966116d4 feat: comma dangle 2022-08-06 23:54:56 +08:00
9abf1b9d73 feat: key spacing 2022-08-06 23:47:28 +08:00
6aaba6debd feat: space between infix op 2022-08-06 23:43:09 +08:00
77565712e0 feat: no-multi-empty-lines 2022-08-06 23:38:03 +08:00
d025259db7 feat: indent 2022-08-06 23:36:20 +08:00
aafdc546fa fix: panic when creating a user in a non-existent org (#969) 2022-08-06 22:30:56 +08:00
539ca2d731 chore(web): add fix command (#964) 2022-08-05 23:40:04 +08:00
ea326b3513 fix: show social buttons on signup page (#962) 2022-08-05 18:59:56 +08:00
98ef766fb4 fix: fix webauthn entry cannot add bug (#960)
* fix: fix webauthn

* Update LoginPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-08-05 17:43:04 +08:00
e94ada9ea2 Fix new accountItem. 2022-08-05 15:36:07 +08:00
4ea482223d feat: add geetest captcha (#953) 2022-08-04 20:55:04 +08:00
d55ae7d1d2 Enable some other DBs 2022-08-04 20:28:09 +08:00
d72e00605f fix: updateProviderField when add provider payment (#952) 2022-08-04 19:39:25 +08:00
be74cb621f feat: Support sub-directory (#943)
By adding PUBLIC_URL to relative `.`

Signed-off-by: zzjin <tczzjin@gmail.com>
2022-08-02 00:21:15 +08:00
13404d6035 feat: fix binding after registration causes the page to crash (#945) 2022-08-01 21:08:10 +08:00
afa9c530ad fix: panic triggered when user is nil (#940) 2022-07-31 23:23:36 +08:00
1600615aca Support sqlite3 DB 2022-07-31 18:11:18 +08:00
2bb8491499 fix: unable to get user if profile is private (#936) 2022-07-31 10:54:41 +08:00
293283ed25 feat: add get user by phone (#934)
* fix: check reset phone & email modify rules

* Update verification.go

* Update organization.go

* feat: add get user by phone

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-31 01:02:28 +08:00
9cb519d1e9 fix: Admins should not be allowed to add third-party login for their members (#932)
* feat: admin can unlink the other user

* feat: global admin can unlink other user

* fix
2022-07-30 23:11:02 +08:00
fb9b8f1662 fix: skip the duplicated users when sync users (#928)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-30 22:24:23 +08:00
2fec3f72ae fix: check reset phone & email modify rules (#927)
* fix: check reset phone & email modify rules

* Update verification.go

* Update organization.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-30 18:17:13 +08:00
11695220a8 Use user.GetId() 2022-07-30 17:40:30 +08:00
155660b0d7 feat: get user api return roles and permissions (#929) 2022-07-30 17:31:56 +08:00
1c72f5300c feat: fix 'Enable code sign' is not displayed in the login page (#925) 2022-07-28 23:11:33 +08:00
3dd56195d9 fix: fix the problem of link error (#923) 2022-07-28 21:52:10 +08:00
8865244262 fix: add oauth login auto close page (#915) 2022-07-26 23:03:55 +08:00
3400fa1e9c feat: support local login for non-built-in users (#911)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-26 19:27:24 +08:00
bdc5c92ef0 fix: send code missing parameter & show more detail responseError (#910) 2022-07-25 23:46:38 +08:00
4e3eedf246 feat: fix bug that the default permission prevents admin to login in (#907)
* fix:The certs page is displayed incorrectly

* Translations for each language are added

* Replace the variables certificat with Certificat with certificate and Certificate

* Replace the variables certificat with Certificat with certificate and Certificate

* Variable names are more accurate

* Variable names are more accurate

* Modify the variable name

* fix: Default action prevents admin to login in
2022-07-24 23:36:55 +08:00
8e98fc5a9f feat: rename all publicKey occurrences to certificate (#894)
* fix:The certs page is displayed incorrectly

* Translations for each language are added

* Replace the variables certificat with Certificat with certificate and Certificate

* Replace the variables certificat with Certificat with certificate and Certificate

* Variable names are more accurate

* Variable names are more accurate

* Modify the variable name
2022-07-23 09:40:51 +08:00
6f6159be07 feat: add GET method of logout API (#903) 2022-07-22 21:13:49 +08:00
3e4dbc2dcb fix: URL bug in getUploadFileUrl function 2022-07-20 17:49:11 +08:00
48b5b27982 fix: invalid redirect url after sign up (#896)
* fix: invalid redirect url after sign up

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

* Update App.js

* Update Setting.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-19 23:31:17 +08:00
1839252c30 chore(web): sort import members (#895) 2022-07-18 20:57:38 +08:00
1fff1db6a7 fix(web): fix the bug of infinity loop animate when unauthorized (#891)
* fix(web): fix the bug of infinity loop when unauthorized

* fix

* fix

* fix

* Update BaseListPage.js

* Update OrganizationListPage.js

* Update OrganizationListPage.js

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-17 18:20:52 +08:00
a0b0e186b7 Improve i18n code and data. 2022-07-17 17:56:43 +08:00
8c7f235ee1 Fix bug in uploadFile()'s URL. 2022-07-17 14:29:06 +08:00
a0a762aa6f fix: typo in field tag in BilibiliUserInfo (#890) 2022-07-17 11:31:43 +08:00
2eec53a6d0 fix: actions initialized to null and model/resources not updated with the owner (#887)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-16 15:00:42 +08:00
117dec4542 feat: failed to sync keycloak users in the PostgreSQL database (#886)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-07-16 12:14:35 +08:00
895cdd024d fix: Typo in user model xorm tag (#883) 2022-07-15 12:01:27 +08:00
f0b0891ac9 feat: query user by userId (#879)
* feat: add `getUserByUserId` func

* Update user.go

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2022-07-14 21:46:13 +08:00
10449e89ab Fix owner bug in GetUser(). 2022-07-13 22:56:35 +08:00
6e70f0fc58 Refactor CheckAccessPermission(). 2022-07-13 00:50:32 +08:00
2bca424370 feat: implement access control using casbin (#806)
* feat: implement access control using casbin

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

* chore: sort imports

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

* fix: remove

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

* Update auth.go

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-07-13 00:34:35 +08:00
de49a45e19 Add escapePath for getUploadFileUrl(). 2022-07-12 23:24:24 +08:00
f7243f879b Fix some JS warnings. 2022-07-12 20:47:11 +08:00
7f3b2500b3 feat: support webauthn (#407)
* feat: support webauthn

* Update init.go

* Update user_webauthn.go

* Update UserEditPage.js

* Update WebauthnCredentialTable.js

* Update LoginPage.js

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2022-07-12 20:06:01 +08:00
211 changed files with 3708 additions and 1354 deletions

View File

@ -68,7 +68,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
Enforcer.ClearPolicy()
//if len(Enforcer.GetPolicy()) == 0 {
// if len(Enforcer.GetPolicy()) == 0 {
if true {
ruleText := `
p, built-in, *, *, *, *, *
@ -78,11 +78,12 @@ p, *, *, POST, /api/get-email-and-phone, *, *
p, *, *, POST, /api/login, *, *
p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/logout, *, *
p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, *
p, *, *, *, /api/login/oauth, *, *
p, *, *, GET, /api/get-application, *, *
p, *, *, GET, /api/get-applications, *, *
p, *, *, GET, /api/get-organization-applications, *, *
p, *, *, GET, /api/get-user, *, *
p, *, *, GET, /api/get-user-application, *, *
p, *, *, GET, /api/get-resources, *, *
@ -91,7 +92,7 @@ p, *, *, POST, /api/buy-product, *, *
p, *, *, GET, /api/get-payment, *, *
p, *, *, POST, /api/update-payment, *, *
p, *, *, POST, /api/invoice-payment, *, *
p, *, *, GET, /api/get-providers, *, *
p, *, *, POST, /api/notify-payment, *, *
p, *, *, POST, /api/unlink, *, *
p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, *
@ -105,6 +106,7 @@ p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /cas, *, *
p, *, *, *, /api/webauthn, *, *
`
sa := stringadapter.NewAdapter(ruleText)

View File

@ -18,7 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"sort"
@ -31,8 +31,7 @@ import (
const AliyunCaptchaVerifyUrl = "http://afs.aliyuncs.com"
type AliyunCaptchaProvider struct {
}
type AliyunCaptchaProvider struct{}
func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
captcha := &AliyunCaptchaProvider{}
@ -81,7 +80,7 @@ func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientSecret string)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}

View File

@ -16,8 +16,7 @@ package captcha
import "github.com/casdoor/casdoor/object"
type DefaultCaptchaProvider struct {
}
type DefaultCaptchaProvider struct{}
func NewDefaultCaptchaProvider() *DefaultCaptchaProvider {
captcha := &DefaultCaptchaProvider{}

81
captcha/geetest.go Normal file
View File

@ -0,0 +1,81 @@
// 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 captcha
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"time"
"github.com/casdoor/casdoor/util"
)
const GEETESTCaptchaVerifyUrl = "http://gcaptcha4.geetest.com/validate"
type GEETESTCaptchaProvider struct{}
func NewGEETESTCaptchaProvider() *GEETESTCaptchaProvider {
captcha := &GEETESTCaptchaProvider{}
return captcha
}
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
pathData, err := url.ParseQuery(token)
if err != nil {
return false, err
}
signToken := util.GetHmacSha256(clientSecret, pathData["lot_number"][0])
formData := make(url.Values)
formData["lot_number"] = []string{pathData["lot_number"][0]}
formData["captcha_output"] = []string{pathData["captcha_output"][0]}
formData["pass_token"] = []string{pathData["pass_token"][0]}
formData["gen_time"] = []string{pathData["gen_time"][0]}
formData["sign_token"] = []string{signToken}
captchaId := pathData["captcha_id"][0]
cli := http.Client{Timeout: time.Second * 5}
resp, err := cli.PostForm(fmt.Sprintf("%s?captcha_id=%s", GEETESTCaptchaVerifyUrl, captchaId), formData)
if err != nil || resp.StatusCode != 200 {
return false, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
type captchaResponse struct {
Result string `json:"result"`
Reason string `json:"reason"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
if captchaResp.Result == "success" {
return true, nil
}
return false, errors.New(captchaResp.Reason)
}

View File

@ -17,7 +17,7 @@ package captcha
import (
"encoding/json"
"errors"
"io/ioutil"
"io"
"net/http"
"net/url"
"strings"
@ -25,8 +25,7 @@ import (
const HCaptchaVerifyUrl = "https://hcaptcha.com/siteverify"
type HCaptchaProvider struct {
}
type HCaptchaProvider struct{}
func NewHCaptchaProvider() *HCaptchaProvider {
captcha := &HCaptchaProvider{}
@ -44,7 +43,7 @@ func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}

View File

@ -27,6 +27,8 @@ func GetCaptchaProvider(captchaType string) CaptchaProvider {
return NewHCaptchaProvider()
} else if captchaType == "Aliyun Captcha" {
return NewAliyunCaptchaProvider()
} else if captchaType == "GEETEST" {
return NewGEETESTCaptchaProvider()
}
return nil
}

View File

@ -17,7 +17,7 @@ package captcha
import (
"encoding/json"
"errors"
"io/ioutil"
"io"
"net/http"
"net/url"
"strings"
@ -25,8 +25,7 @@ import (
const ReCaptchaVerifyUrl = "https://recaptcha.net/recaptcha/api/siteverify"
type ReCaptchaProvider struct {
}
type ReCaptchaProvider struct{}
func NewReCaptchaProvider() *ReCaptchaProvider {
captcha := &ReCaptchaProvider{}
@ -44,7 +43,7 @@ func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientSecret string) (boo
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}

View File

@ -48,8 +48,8 @@ func GetConfigInt64(key string) (int64, error) {
}
func init() {
//this array contains the beego configuration items that may be modified via env
var presetConfigItems = []string{"httpport", "appname"}
// this array contains the beego configuration items that may be modified via env
presetConfigItems := []string{"httpport", "appname"}
for _, key := range presetConfigItems {
if value, ok := os.LookupEnv(key); ok {
beego.AppConfig.Set(key, value)

View File

@ -32,7 +32,7 @@ func TestGetConfString(t *testing.T) {
{"Should be return value", "key", "value"},
}
//do some set up job
// do some set up job
os.Setenv("appname", "casbin")
os.Setenv("key", "value")
@ -58,7 +58,7 @@ func TestGetConfInt(t *testing.T) {
{"Should be return 8000", "verificationCodeTimeout", 10},
}
//do some set up job
// do some set up job
os.Setenv("httpport", "8001")
err := beego.LoadAppConfig("ini", "app.conf")

View File

@ -217,7 +217,7 @@ func (c *ApiController) Signup() {
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
userId := fmt.Sprintf("%s/%s", user.Owner, user.Name)
userId := user.GetId()
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
c.ResponseOk(userId)
@ -228,7 +228,7 @@ func (c *ApiController) Signup() {
// @Tag Login API
// @Description logout the current user
// @Success 200 {object} controllers.Response The Response object
// @router /logout [post]
// @router /logout [get,post]
func (c *ApiController) Logout() {
user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
@ -274,6 +274,7 @@ func (c *ApiController) GetAccount() {
c.ServeJSON()
}
// GetUserinfo
// UserInfo
// @Title UserInfo
// @Tag Account API

View File

@ -94,6 +94,29 @@ func (c *ApiController) GetUserApplication() {
c.ServeJSON()
}
// GetOrganizationApplications
// @Title GetOrganizationApplications
// @Tag Application API
// @Description get the detail of the organization's application
// @Param organization query string true "The organization name"
// @Success 200 {array} object.Application The Response object
// @router /get-organization-applications [get]
func (c *ApiController) GetOrganizationApplications() {
userId := c.GetSessionUsername()
owner := c.Input().Get("owner")
organization := c.Input().Get("organization")
if organization == "" {
c.ResponseError("Parameter organization is missing")
return
}
var applications []*object.Application
applications = object.GetApplicationsByOrganizationName(owner, organization)
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
}
// UpdateApplication
// @Title UpdateApplication
// @Tag Application API

View File

@ -44,12 +44,22 @@ func tokenToResponse(token *object.Token) *Response {
return &Response{Status: "error", Msg: "fail to get accessToken", Data: token.AccessToken}
}
return &Response{Status: "ok", Msg: "", Data: token.AccessToken}
}
// HandleLoggedIn ...
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *RequestForm) (resp *Response) {
userId := user.GetId()
allowed, err := object.CheckAccessPermission(userId, application)
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
if !allowed {
c.ResponseError("Unauthorized operation")
return
}
if form.Type == ResponseTypeLogin {
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
@ -75,7 +85,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// The prompt page needs the user to be signed in
c.SetSessionUsername(userId)
}
} else if form.Type == ResponseTypeToken || form.Type == ResponseTypeIdToken { //implicit flow
} else if form.Type == ResponseTypeToken || form.Type == ResponseTypeIdToken { // implicit flow
if !object.IsGrantTypeValid(form.Type, application.GrantTypes) {
resp = &Response{Status: "error", Msg: fmt.Sprintf("error: grant_type: %s is not supported in this application", form.Type), Data: ""}
} else {
@ -83,7 +93,6 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
token, _ := object.GetTokenByUser(application, user, scope, c.Ctx.Request.Host)
resp = tokenToResponse(token)
}
} else if form.Type == ResponseTypeSaml { // saml flow
res, redirectUrl, err := object.GetSamlResponse(application, user, form.SamlRequest, c.Ctx.Request.Host)
if err != nil {
@ -92,7 +101,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
}
resp = &Response{Status: "ok", Msg: "", Data: res, Data2: redirectUrl}
} else if form.Type == ResponseTypeCas {
//not oauth but CAS SSO protocol
// not oauth but CAS SSO protocol
service := c.Input().Get("service")
resp = wrapErrorResponse(nil)
if service != "" {
@ -109,7 +118,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
}
} else {
resp = wrapErrorResponse(fmt.Errorf("Unknown response type: %s", form.Type))
resp = wrapErrorResponse(fmt.Errorf("unknown response type: %s", form.Type))
}
// if user did not check auto signin
@ -419,7 +428,7 @@ func (c *ApiController) Login() {
} else if provider.Category == "SAML" {
resp = &Response{Status: "error", Msg: "The account does not exist"}
}
//resp = &Response{Status: "ok", Msg: "", Data: res}
// resp = &Response{Status: "ok", Msg: "", Data: res}
} else { // form.Method != "signup"
userId := c.GetSessionUsername()
if userId == "" {

View File

@ -23,11 +23,13 @@ import (
"github.com/casdoor/casdoor/util"
)
// ApiController
// controller for handlers under /api uri
type ApiController struct {
beego.Controller
}
// RootController
// controller for handlers directly under / (root)
type RootController struct {
ApiController
@ -136,9 +138,9 @@ func (c *ApiController) SetSessionData(s *SessionData) {
func wrapActionResponse(affected bool) *Response {
if affected {
return &Response{Status: "ok", Msg: "", Data: "Affected"}
return &Response{Status: "ok", Msg: ""}
} else {
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
return &Response{Status: "error", Msg: "this operation has no effect"}
}
}

View File

@ -31,7 +31,7 @@ const (
InvalidProxyCallback string = "INVALID_PROXY_CALLBACK"
InvalidTicket string = "INVALID_TICKET"
InvalidService string = "INVALID_SERVICE"
InteralError string = "INTERNAL_ERROR"
InternalError string = "INTERNAL_ERROR"
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
)
@ -44,14 +44,13 @@ func (c *RootController) CasValidate() {
return
}
if ok, response, issuedService, _ := object.GetCasTokenByTicket(ticket); ok {
//check whether service is the one for which we previously issued token
// 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
// token not found
c.Ctx.Output.Body([]byte("no\n"))
}
@ -83,41 +82,41 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
Xmlns: "http://www.yale.edu/tp/cas",
}
//check whether all required parameters are met
// check whether all required parameters are met
if service == "" || ticket == "" {
c.sendCasAuthenticationResponseErr(InvalidRequest, "service and ticket must exist", format)
return
}
ok, response, issuedService, userId := object.GetCasTokenByTicket(ticket)
//find the token
// find the token
if ok {
//check whether service is the one for which we previously issued token
// check whether service is the one for which we previously issued token
if strings.HasPrefix(service, issuedService) {
serviceResponse.Success = response
} else {
//service not match
// service not match
c.sendCasAuthenticationResponseErr(InvalidService, fmt.Sprintf("service %s and %s does not match", service, issuedService), format)
return
}
} else {
//token not found
// 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
// that means we are in proxy web flow
pgt := object.StoreCasTokenForPgt(serviceResponse.Success, service, userId)
pgtiou := serviceResponse.Success.ProxyGrantingTicket
//todo: check whether it is https
// 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
// make a request to pgturl passing pgt and pgtiou
if err != nil {
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
c.sendCasAuthenticationResponseErr(InternalError, err.Error(), format)
return
}
param := pgtUrlObj.Query()
@ -127,13 +126,13 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
request, err := http.NewRequest("GET", pgtUrlObj.String(), nil)
if err != nil {
c.sendCasAuthenticationResponseErr(InteralError, err.Error(), format)
c.sendCasAuthenticationResponseErr(InternalError, err.Error(), format)
return
}
resp, err := http.DefaultClient.Do(request)
if err != nil || !(resp.StatusCode >= 200 && resp.StatusCode < 400) {
//failed to send request
// failed to send request
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
return
}
@ -184,7 +183,6 @@ func (c *RootController) CasProxy() {
c.Data["xml"] = serviceResponse
c.ServeXML()
}
}
func (c *RootController) SamlValidate() {
@ -216,7 +214,7 @@ func (c *RootController) SamlValidate() {
return
}
envelopReponse := struct {
envelopResponse := struct {
XMLName xml.Name `xml:"SOAP-ENV:Envelope"`
Xmlns string `xml:"xmlns:SOAP-ENV"`
Body struct {
@ -224,15 +222,15 @@ func (c *RootController) SamlValidate() {
Content string `xml:",innerxml"`
}
}{}
envelopReponse.Xmlns = "http://schemas.xmlsoap.org/soap/envelope/"
envelopReponse.Body.Content = response
envelopResponse.Xmlns = "http://schemas.xmlsoap.org/soap/envelope/"
envelopResponse.Body.Content = response
data, err := xml.Marshal(envelopReponse)
data, err := xml.Marshal(envelopResponse)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Ctx.Output.Body([]byte(data))
c.Ctx.Output.Body(data)
}
func (c *RootController) sendCasProxyResponseErr(code, msg, format string) {

View File

@ -48,6 +48,7 @@ func (c *ApiController) GetCerts() {
}
}
// GetCert
// @Title GetCert
// @Tag Cert API
// @Description get cert
@ -61,6 +62,7 @@ func (c *ApiController) GetCert() {
c.ServeJSON()
}
// UpdateCert
// @Title UpdateCert
// @Tag Cert API
// @Description update cert
@ -81,6 +83,7 @@ func (c *ApiController) UpdateCert() {
c.ServeJSON()
}
// AddCert
// @Title AddCert
// @Tag Cert API
// @Description add cert
@ -98,6 +101,7 @@ func (c *ApiController) AddCert() {
c.ServeJSON()
}
// DeleteCert
// @Title DeleteCert
// @Tag Cert API
// @Description delete cert

88
controllers/enforcer.go Normal file
View File

@ -0,0 +1,88 @@
// 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 (
"encoding/json"
"github.com/casdoor/casdoor/object"
)
func (c *ApiController) Enforce() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please sign in first")
return
}
var permissionRule object.PermissionRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRule)
if err != nil {
panic(err)
}
c.Data["json"] = object.Enforce(userId, &permissionRule)
c.ServeJSON()
}
func (c *ApiController) BatchEnforce() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please sign in first")
return
}
var permissionRules []object.PermissionRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRules)
if err != nil {
panic(err)
}
c.Data["json"] = object.BatchEnforce(userId, permissionRules)
c.ServeJSON()
}
func (c *ApiController) GetAllObjects() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please sign in first")
return
}
c.Data["json"] = object.GetAllObjects(userId)
c.ServeJSON()
}
func (c *ApiController) GetAllActions() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please sign in first")
return
}
c.Data["json"] = object.GetAllActions(userId)
c.ServeJSON()
}
func (c *ApiController) GetAllRoles() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please sign in first")
return
}
c.Data["json"] = object.GetAllRoles(userId)
c.ServeJSON()
}

View File

@ -30,7 +30,7 @@ type LdapServer struct {
}
type LdapResp struct {
//Groups []LdapRespGroup `json:"groups"`
// Groups []LdapRespGroup `json:"groups"`
Users []object.LdapRespUser `json:"users"`
}
@ -44,6 +44,7 @@ type LdapSyncResp struct {
Failed []object.LdapRespUser `json:"failed"`
}
// GetLdapUser
// @Tag Account API
// @Title GetLdapser
// @router /get-ldap-user [post]
@ -88,7 +89,7 @@ func (c *ApiController) GetLdapUser() {
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
//GroupName: groupsMap[user.GidNumber].Cn,
// GroupName: groupsMap[user.GidNumber].Cn,
Uuid: user.Uuid,
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
@ -100,6 +101,7 @@ func (c *ApiController) GetLdapUser() {
c.ServeJSON()
}
// GetLdaps
// @Tag Account API
// @Title GetLdaps
// @router /get-ldaps [post]
@ -110,6 +112,7 @@ func (c *ApiController) GetLdaps() {
c.ServeJSON()
}
// GetLdap
// @Tag Account API
// @Title GetLdap
// @router /get-ldap [post]
@ -125,6 +128,7 @@ func (c *ApiController) GetLdap() {
c.ServeJSON()
}
// AddLdap
// @Tag Account API
// @Title AddLdap
// @router /add-ldap [post]
@ -159,6 +163,7 @@ func (c *ApiController) AddLdap() {
c.ServeJSON()
}
// UpdateLdap
// @Tag Account API
// @Title UpdateLdap
// @router /update-ldap [post]
@ -186,6 +191,7 @@ func (c *ApiController) UpdateLdap() {
c.ServeJSON()
}
// DeleteLdap
// @Tag Account API
// @Title DeleteLdap
// @router /delete-ldap [post]
@ -201,6 +207,7 @@ func (c *ApiController) DeleteLdap() {
c.ServeJSON()
}
// SyncLdapUsers
// @Tag Account API
// @Title SyncLdapUsers
// @router /sync-ldap-users [post]
@ -223,6 +230,7 @@ func (c *ApiController) SyncLdapUsers() {
c.ServeJSON()
}
// CheckLdapUsersExist
// @Tag Account API
// @Title CheckLdapUserExist
// @router /check-ldap-users-exist [post]

View File

@ -21,7 +21,8 @@ import (
)
type LinkForm struct {
ProviderType string `json:"providerType"`
ProviderType string `json:"providerType"`
User object.User `json:"user"`
}
// Unlink ...
@ -40,16 +41,55 @@ func (c *ApiController) Unlink() {
}
providerType := form.ProviderType
// the user will be unlinked from the provider
unlinkedUser := form.User
user := object.GetUser(userId)
value := object.GetUserField(user, providerType)
if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin {
// if the user is not the same as the one we are unlinking, we need to make sure the user is the global admin.
c.ResponseError("You are not the global admin, you can't unlink other users")
return
}
if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin {
// if the user is unlinking themselves, should check the provider can be unlinked, if not, we should return an error.
application := object.GetApplicationByUser(user)
if application == nil {
c.ResponseError("You can't unlink yourself, you are not a member of any application")
return
}
if len(application.Providers) == 0 {
c.ResponseError("This application has no providers")
return
}
provider := application.GetProviderItemByType(providerType)
if provider == nil {
c.ResponseError("This application has no providers of type " + providerType)
return
}
if !provider.CanUnlink {
c.ResponseError("This provider can't be unlinked")
return
}
}
// only two situations can happen here
// 1. the user is the global admin
// 2. the user is unlinking themselves and provider can be unlinked
value := object.GetUserField(&unlinkedUser, providerType)
if value == "" {
c.ResponseError("Please link first", value)
return
}
object.ClearUserOAuthProperties(user, providerType)
object.ClearUserOAuthProperties(&unlinkedUser, providerType)
object.LinkUserAccount(user, providerType, "")
object.LinkUserAccount(&unlinkedUser, providerType, "")
c.ResponseOk()
}

View File

@ -16,6 +16,7 @@ package controllers
import "github.com/casdoor/casdoor/object"
// GetOidcDiscovery
// @Title GetOidcDiscovery
// @Tag OIDC API
// @Description Get Oidc Discovery
@ -27,6 +28,7 @@ func (c *RootController) GetOidcDiscovery() {
c.ServeJSON()
}
// GetJwks
// @Title GetJwks
// @Tag OIDC API
// @Success 200 {object} jose.JSONWebKey

View File

@ -67,6 +67,7 @@ func (c *ApiController) GetUserPayments() {
c.ResponseOk(payments)
}
// GetPayment
// @Title GetPayment
// @Tag Payment API
// @Description get payment
@ -80,6 +81,7 @@ func (c *ApiController) GetPayment() {
c.ServeJSON()
}
// UpdatePayment
// @Title UpdatePayment
// @Tag Payment API
// @Description update payment
@ -100,6 +102,7 @@ func (c *ApiController) UpdatePayment() {
c.ServeJSON()
}
// AddPayment
// @Title AddPayment
// @Tag Payment API
// @Description add payment
@ -117,6 +120,7 @@ func (c *ApiController) AddPayment() {
c.ServeJSON()
}
// DeletePayment
// @Title DeletePayment
// @Tag Payment API
// @Description delete payment
@ -134,6 +138,7 @@ func (c *ApiController) DeletePayment() {
c.ServeJSON()
}
// NotifyPayment
// @Title NotifyPayment
// @Tag Payment API
// @Description notify payment
@ -159,6 +164,7 @@ func (c *ApiController) NotifyPayment() {
}
}
// InvoicePayment
// @Title InvoicePayment
// @Tag Payment API
// @Description invoice payment

View File

@ -48,6 +48,7 @@ func (c *ApiController) GetPermissions() {
}
}
// GetPermission
// @Title GetPermission
// @Tag Permission API
// @Description get permission
@ -61,6 +62,7 @@ func (c *ApiController) GetPermission() {
c.ServeJSON()
}
// UpdatePermission
// @Title UpdatePermission
// @Tag Permission API
// @Description update permission
@ -81,6 +83,7 @@ func (c *ApiController) UpdatePermission() {
c.ServeJSON()
}
// AddPermission
// @Title AddPermission
// @Tag Permission API
// @Description add permission
@ -98,6 +101,7 @@ func (c *ApiController) AddPermission() {
c.ServeJSON()
}
// DeletePermission
// @Title DeletePermission
// @Tag Permission API
// @Description delete permission

View File

@ -49,6 +49,7 @@ func (c *ApiController) GetProducts() {
}
}
// GetProduct
// @Title GetProduct
// @Tag Product API
// @Description get product
@ -58,10 +59,14 @@ func (c *ApiController) GetProducts() {
func (c *ApiController) GetProduct() {
id := c.Input().Get("id")
c.Data["json"] = object.GetProduct(id)
product := object.GetProduct(id)
object.ExtendProductWithProviders(product)
c.Data["json"] = product
c.ServeJSON()
}
// UpdateProduct
// @Title UpdateProduct
// @Tag Product API
// @Description update product
@ -82,6 +87,7 @@ func (c *ApiController) UpdateProduct() {
c.ServeJSON()
}
// AddProduct
// @Title AddProduct
// @Tag Product API
// @Description add product
@ -99,6 +105,7 @@ func (c *ApiController) AddProduct() {
c.ServeJSON()
}
// DeleteProduct
// @Title DeleteProduct
// @Tag Product API
// @Description delete product
@ -116,6 +123,7 @@ func (c *ApiController) DeleteProduct() {
c.ServeJSON()
}
// BuyProduct
// @Title BuyProduct
// @Tag Product API
// @Description buy product

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
@ -47,6 +48,7 @@ func (c *ApiController) GetProviders() {
}
}
// GetProvider
// @Title GetProvider
// @Tag Provider API
// @Description get provider
@ -60,6 +62,7 @@ func (c *ApiController) GetProvider() {
c.ServeJSON()
}
// UpdateProvider
// @Title UpdateProvider
// @Tag Provider API
// @Description update provider
@ -80,6 +83,7 @@ func (c *ApiController) UpdateProvider() {
c.ServeJSON()
}
// AddProvider
// @Title AddProvider
// @Tag Provider API
// @Description add provider
@ -97,6 +101,7 @@ func (c *ApiController) AddProvider() {
c.ServeJSON()
}
// DeleteProvider
// @Title DeleteProvider
// @Tag Provider API
// @Description delete provider

View File

@ -27,6 +27,7 @@ import (
"github.com/casdoor/casdoor/util"
)
// GetResources
// @router /get-resources [get]
// @Tag Resource API
// @Title GetResources
@ -50,6 +51,7 @@ func (c *ApiController) GetResources() {
}
}
// GetResource
// @Tag Resource API
// @Title GetResource
// @router /get-resource [get]
@ -60,6 +62,7 @@ func (c *ApiController) GetResource() {
c.ServeJSON()
}
// UpdateResource
// @Tag Resource API
// @Title UpdateResource
// @router /update-resource [post]
@ -76,6 +79,7 @@ func (c *ApiController) UpdateResource() {
c.ServeJSON()
}
// AddResource
// @Tag Resource API
// @Title AddResource
// @router /add-resource [post]
@ -90,6 +94,7 @@ func (c *ApiController) AddResource() {
c.ServeJSON()
}
// DeleteResource
// @Tag Resource API
// @Title DeleteResource
// @router /delete-resource [post]
@ -115,6 +120,7 @@ func (c *ApiController) DeleteResource() {
c.ServeJSON()
}
// UploadResource
// @Tag Resource API
// @Title UploadResource
// @router /upload-resource [post]

View File

@ -48,6 +48,7 @@ func (c *ApiController) GetRoles() {
}
}
// GetRole
// @Title GetRole
// @Tag Role API
// @Description get role
@ -61,6 +62,7 @@ func (c *ApiController) GetRole() {
c.ServeJSON()
}
// UpdateRole
// @Title UpdateRole
// @Tag Role API
// @Description update role
@ -81,6 +83,7 @@ func (c *ApiController) UpdateRole() {
c.ServeJSON()
}
// AddRole
// @Title AddRole
// @Tag Role API
// @Description add role
@ -98,6 +101,7 @@ func (c *ApiController) AddRole() {
c.ServeJSON()
}
// DeleteRole
// @Title DeleteRole
// @Tag Role API
// @Description delete role

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
@ -47,6 +48,7 @@ func (c *ApiController) GetSyncers() {
}
}
// GetSyncer
// @Title GetSyncer
// @Tag Syncer API
// @Description get syncer
@ -60,6 +62,7 @@ func (c *ApiController) GetSyncer() {
c.ServeJSON()
}
// UpdateSyncer
// @Title UpdateSyncer
// @Tag Syncer API
// @Description update syncer
@ -80,6 +83,7 @@ func (c *ApiController) UpdateSyncer() {
c.ServeJSON()
}
// AddSyncer
// @Title AddSyncer
// @Tag Syncer API
// @Description add syncer
@ -97,6 +101,7 @@ func (c *ApiController) AddSyncer() {
c.ServeJSON()
}
// DeleteSyncer
// @Title DeleteSyncer
// @Tag Syncer API
// @Description delete syncer
@ -114,6 +119,7 @@ func (c *ApiController) DeleteSyncer() {
c.ServeJSON()
}
// RunSyncer
// @Title RunSyncer
// @Tag Syncer API
// @Description run syncer

View File

@ -255,7 +255,7 @@ func (c *ApiController) RefreshToken() {
// @router /login/oauth/logout [get]
func (c *ApiController) TokenLogout() {
token := c.Input().Get("id_token_hint")
flag, application := object.DeleteTokenByAceessToken(token)
flag, application := object.DeleteTokenByAccessToken(token)
redirectUri := c.Input().Get("post_logout_redirect_uri")
state := c.Input().Get("state")
if application != nil && object.CheckRedirectUriValid(application, redirectUri) {
@ -288,7 +288,7 @@ func (c *ApiController) IntrospectToken() {
if clientId == "" || clientSecret == "" {
c.ResponseError("empty clientId or clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_REQUEST,
Error: object.InvalidRequest,
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
@ -299,7 +299,7 @@ func (c *ApiController) IntrospectToken() {
if application == nil || application.ClientSecret != clientSecret {
c.ResponseError("invalid application or wrong clientSecret")
c.Data["json"] = &object.TokenError{
Error: object.INVALID_CLIENT,
Error: object.InvalidClient,
}
c.SetTokenErrorHttpStatus()
return

View File

@ -80,19 +80,27 @@ func (c *ApiController) GetUsers() {
// @Title GetUser
// @Tag User API
// @Description get user
// @Param id query string true "The id of the user"
// @Param id query string true "The id of the user"
// @Param owner query string false "The owner of the user"
// @Param email query string false "The email of the user"
// @Param phone query string false "The phone of the user"
// @Success 200 {object} object.User The Response object
// @router /get-user [get]
func (c *ApiController) GetUser() {
id := c.Input().Get("id")
owner := c.Input().Get("owner")
email := c.Input().Get("email")
userOwner, _ := util.GetOwnerAndNameFromId(id)
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", userOwner))
phone := c.Input().Get("phone")
userId := c.Input().Get("userId")
owner := c.Input().Get("owner")
if owner == "" {
owner, _ = util.GetOwnerAndNameFromId(id)
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false)
hasPermission, err := object.CheckUserPermission(requestUserId, id, owner, false)
if !hasPermission {
c.ResponseError(err.Error())
return
@ -100,10 +108,22 @@ func (c *ApiController) GetUser() {
}
var user *object.User
if email == "" {
user = object.GetUser(id)
} else {
switch {
case email != "":
user = object.GetUserByEmail(owner, email)
case phone != "":
user = object.GetUserByPhone(owner, phone)
case userId != "":
user = object.GetUserByUserId(owner, userId)
default:
user = object.GetUser(id)
}
if user != nil {
roles := object.GetRolesByUser(user.GetId())
user.Roles = roles
permissions := object.GetPermissionsByUser(user.GetId())
user.Permissions = permissions
}
c.Data["json"] = object.GetMaskedUser(user)
@ -246,7 +266,7 @@ func (c *ApiController) SetPassword() {
requestUserId := c.GetSessionUsername()
userId := fmt.Sprintf("%s/%s", userOwner, userName)
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true)
hasPermission, err := object.CheckUserPermission(requestUserId, userId, userOwner, true)
if !hasPermission {
c.ResponseError(err.Error())
return
@ -278,6 +298,7 @@ func (c *ApiController) SetPassword() {
c.ServeJSON()
}
// CheckUserPassword
// @Title CheckUserPassword
// @router /check-user-password [post]
// @Tag User API

View File

@ -55,7 +55,7 @@ func (c *ApiController) ResponseError(error string, data ...interface{}) {
func (c *ApiController) SetTokenErrorHttpStatus() {
_, ok := c.Data["json"].(*object.TokenError)
if ok {
if c.Data["json"].(*object.TokenError).Error == object.INVALID_CLIENT {
if c.Data["json"].(*object.TokenError).Error == object.InvalidClient {
c.Ctx.Output.SetStatus(401)
c.Ctx.Output.Header("WWW-Authenticate", "Basic realm=\"OAuth2\"")
} else {

View File

@ -49,8 +49,24 @@ func (c *ApiController) SendVerificationCode() {
applicationId := c.Ctx.Request.Form.Get("applicationId")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if destType == "" || dest == "" || applicationId == "" || !strings.Contains(applicationId, "/") || checkType == "" {
c.ResponseError("Missing parameter.")
if destType == "" {
c.ResponseError("Missing parameter: type.")
return
}
if dest == "" {
c.ResponseError("Missing parameter: dest.")
return
}
if applicationId == "" {
c.ResponseError("Missing parameter: applicationId.")
return
}
if !strings.Contains(applicationId, "/") {
c.ResponseError("Wrong parameter: applicationId.")
return
}
if checkType == "" {
c.ResponseError("Missing parameter: checkType.")
return
}
@ -82,7 +98,7 @@ func (c *ApiController) SendVerificationCode() {
return
}
sendResp := errors.New("Invalid dest type")
sendResp := errors.New("invalid dest type")
if user == nil && checkUser != "" && checkUser != "true" {
name := application.Organization
@ -152,13 +168,35 @@ func (c *ApiController) ResetEmailOrPhone() {
}
checkDest := dest
org := object.GetOrganizationByUser(user)
if destType == "phone" {
org := object.GetOrganizationByUser(user)
phoneItem := object.GetAccountItemByName("Phone", org)
if phoneItem == nil {
c.ResponseError("Unable to get the phone modify rule.")
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user); !pass {
c.ResponseError(errMsg)
return
}
phonePrefix := "86"
if org != nil && org.PhonePrefix != "" {
phonePrefix = org.PhonePrefix
}
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
} else if destType == "email" {
emailItem := object.GetAccountItemByName("Email", org)
if emailItem == nil {
c.ResponseError("Unable to get the email modify rule.")
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user); !pass {
c.ResponseError(errMsg)
return
}
}
if ret := object.CheckVerificationCode(checkDest, code); len(ret) != 0 {
c.ResponseError(ret)

142
controllers/webauthn.go Normal file
View File

@ -0,0 +1,142 @@
// 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 (
"bytes"
"io"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn"
)
// WebAuthnSignupBegin
// @Title WebAuthnSignupBegin
// @Tag User API
// @Description WebAuthn Registration Flow 1st stage
// @Success 200 {object} protocol.CredentialCreation The CredentialCreationOptions object
// @router /webauthn/signup/begin [get]
func (c *ApiController) WebAuthnSignupBegin() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
user := c.getCurrentUser()
if user == nil {
c.ResponseError("Please login first.")
return
}
registerOptions := func(credCreationOpts *protocol.PublicKeyCredentialCreationOptions) {
credCreationOpts.CredentialExcludeList = user.CredentialExcludeList()
}
options, sessionData, err := webauthnObj.BeginRegistration(
user,
registerOptions,
)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSession("registration", *sessionData)
c.Data["json"] = options
c.ServeJSON()
}
// WebAuthnSignupFinish
// @Title WebAuthnSignupFinish
// @Tag User API
// @Description WebAuthn Registration Flow 2nd stage
// @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response"
// @Success 200 {object} Response "The Response object"
// @router /webauthn/signup/finish [post]
func (c *ApiController) WebAuthnSignupFinish() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
user := c.getCurrentUser()
if user == nil {
c.ResponseError("Please login first.")
return
}
sessionObj := c.GetSession("registration")
sessionData, ok := sessionObj.(webauthn.SessionData)
if !ok {
c.ResponseError("Please call WebAuthnSignupBegin first")
return
}
c.Ctx.Request.Body = io.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
credential, err := webauthnObj.FinishRegistration(user, sessionData, c.Ctx.Request)
if err != nil {
c.ResponseError(err.Error())
return
}
isGlobalAdmin := c.IsGlobalAdmin()
user.AddCredentials(*credential, isGlobalAdmin)
c.ResponseOk()
}
// WebAuthnSigninBegin
// @Title WebAuthnSigninBegin
// @Tag Login API
// @Description WebAuthn Login Flow 1st stage
// @Param owner query string true "owner"
// @Param name query string true "name"
// @Success 200 {object} protocol.CredentialAssertion The CredentialAssertion object
// @router /webauthn/signin/begin [get]
func (c *ApiController) WebAuthnSigninBegin() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
userOwner := c.Input().Get("owner")
userName := c.Input().Get("name")
user := object.GetUserByFields(userOwner, userName)
if user == nil {
c.ResponseError("Please Giveout Owner and Username.")
return
}
options, sessionData, err := webauthnObj.BeginLogin(user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSession("authentication", *sessionData)
c.Data["json"] = options
c.ServeJSON()
}
// WebAuthnSigninFinish
// @Title WebAuthnSigninBegin
// @Tag Login API
// @Description WebAuthn Login Flow 2nd stage
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
// @Success 200 {object} Response "The Response object"
// @router /webauthn/signin/finish [post]
func (c *ApiController) WebAuthnSigninFinish() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
sessionObj := c.GetSession("authentication")
sessionData, ok := sessionObj.(webauthn.SessionData)
if !ok {
c.ResponseError("Please call WebAuthnSigninBegin first")
return
}
c.Ctx.Request.Body = io.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
userId := string(sessionData.UserID)
user := object.GetUser(userId)
_, err := webauthnObj.FinishLogin(user, sessionData, c.Ctx.Request)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
c.ResponseOk(userId)
}

View File

@ -48,6 +48,7 @@ func (c *ApiController) GetWebhooks() {
}
}
// GetWebhook
// @Title GetWebhook
// @Tag Webhook API
// @Description get webhook
@ -61,6 +62,7 @@ func (c *ApiController) GetWebhook() {
c.ServeJSON()
}
// UpdateWebhook
// @Title UpdateWebhook
// @Tag Webhook API
// @Description update webhook
@ -81,6 +83,7 @@ func (c *ApiController) UpdateWebhook() {
c.ServeJSON()
}
// AddWebhook
// @Title AddWebhook
// @Tag Webhook API
// @Description add webhook
@ -98,6 +101,7 @@ func (c *ApiController) AddWebhook() {
c.ServeJSON()
}
// DeleteWebhook
// @Title DeleteWebhook
// @Tag Webhook API
// @Description delete webhook

View File

@ -24,7 +24,6 @@ func NewArgon2idCredManager() *Argon2idCredManager {
}
func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil {
return ""

View File

@ -17,6 +17,7 @@ package cred
import (
"crypto/sha256"
"encoding/base64"
"golang.org/x/crypto/pbkdf2"
)
@ -36,4 +37,4 @@ func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, userSalt str
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
}
}

3
go.mod
View File

@ -14,6 +14,8 @@ require (
github.com/casdoor/goth v1.69.0-FIX2
github.com/casdoor/oss v1.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-pay/gopay v1.5.72
@ -22,6 +24,7 @@ require (
github.com/google/uuid v1.2.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v0.9.0
github.com/lib/pq v1.8.0
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1

16
go.sum
View File

@ -111,6 +111,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7 h1:Puu1hUwfps3+1CUzYdAZXijuvLuRMirgiXdf3zsM2Ig=
github.com/cloudflare/cfssl v0.0.0-20190726000631-633726f6bcb7/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
@ -123,7 +125,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc h1:VRRKCwnzqk8QCaRC4os14xoKDdbHqqlJtJA0oc1ZAjg=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b h1:L63RATZFZuFMXy6ixnKmv3eNAXwYQF6HW1vd4IYsQqQ=
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b/go.mod h1:EYSpSkwoEcryMmQGfhol2IiB3IMN9IIIaNd/wcAQMGQ=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk=
@ -135,6 +140,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ=
github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
@ -164,8 +171,10 @@ 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/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/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
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/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -201,6 +210,8 @@ github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNu
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -298,6 +309,8 @@ github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJK
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -397,6 +410,8 @@ github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnD
github.com/volcengine/volc-sdk-golang v1.0.19 h1:jJp+aJgK0e//rZ9I0K2Y7ufJwvuZRo/AQsYDynXMNgA=
github.com/volcengine/volc-sdk-golang v1.0.19/go.mod h1:+GGi447k4p1I5PNdbpG2GLaF0Ui9vIInTojMM0IfSS4=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -413,6 +428,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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=

View File

@ -39,6 +39,7 @@ func readI18nFile(language string) *I18nData {
func writeI18nFile(language string, data *I18nData) {
s := util.StructToJsonFormatted(data)
s = strings.ReplaceAll(s, "\\u0026", "&")
s += "\n"
println(s)
util.WriteStringToPath(s, getI18nFilePath(language))

View File

@ -19,7 +19,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"time"
@ -59,12 +59,12 @@ func (idp *AdfsIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *AdfsIdProvider) getConfig(hostUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
AuthURL: fmt.Sprintf("%s/adfs/oauth2/authorize", hostUrl),
TokenURL: fmt.Sprintf("%s/adfs/oauth2/token", hostUrl),
}
var config = &oauth2.Config{
config := &oauth2.Config{
Endpoint: endpoint,
}
@ -77,6 +77,7 @@ type AdfsToken struct {
ErrMsg string `json:"error_description"`
}
// GetToken
// get more detail via: https://docs.microsoft.com/en-us/windows-server/identity/ad-fs/overview/ad-fs-openid-connect-oauth-flows-scenarios#request-an-access-token
func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
payload := url.Values{}
@ -88,7 +89,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -109,6 +110,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
return token, nil
}
// GetUserInfo
// Since the userinfo endpoint of ADFS only returns sub,
// the id_token is used to resolve the userinfo
func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
@ -122,10 +124,10 @@ func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
tokenSrc := []byte(token.AccessToken)
publicKey, _ := keyset.Keys[0].Materialize()
id_token, _ := jwt.Parse(bytes.NewReader(tokenSrc), jwt.WithVerify(jwa.RS256, publicKey))
sid, _ := id_token.Get("sid")
upn, _ := id_token.Get("upn")
name, _ := id_token.Get("unique_name")
idToken, _ := jwt.Parse(bytes.NewReader(tokenSrc), jwt.WithVerify(jwa.RS256, publicKey))
sid, _ := idToken.Get("sid")
upn, _ := idToken.Get("upn")
name, _ := idToken.Get("unique_name")
userinfo := &UserInfo{
Id: sid.(string),
Username: name.(string),

View File

@ -24,7 +24,6 @@ import (
"encoding/json"
"encoding/pem"
"io"
"io/ioutil"
"net/http"
"net/url"
"sort"
@ -56,12 +55,12 @@ func (idp *AlipayIdProvider) SetHttpClient(client *http.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{
endpoint := oauth2.Endpoint{
AuthURL: "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm",
TokenURL: "https://openapi.alipay.com/gateway.do",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"", ""},
Endpoint: endpoint,
ClientID: clientId,
@ -205,8 +204,7 @@ func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"golang.org/x/oauth2"
@ -46,12 +46,12 @@ func (idp *BaiduIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *BaiduIdProvider) getConfig() *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
AuthURL: "https://openapi.baidu.com/oauth/2.0/authorize",
TokenURL: "https://openapi.baidu.com/oauth/2.0/token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"email"},
Endpoint: endpoint,
}
@ -97,7 +97,7 @@ func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
@ -47,12 +46,12 @@ func (idp *BilibiliIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *BilibiliIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://api.bilibili.com/x/account-oauth2/v1/token",
AuthURL: "http://member.bilibili.com/arcopen/fn/user/account/info",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"", ""},
Endpoint: endpoint,
ClientID: clientId,
@ -76,6 +75,7 @@ type BilibiliIdProviderTokenResponse struct {
Data BilibiliProviderToken `json:"data"`
}
// GetToken
/*
{
"code": 0,
@ -104,7 +104,6 @@ func (idp *BilibiliIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
data, err := idp.postWithBody(pTokenParams, idp.Config.Endpoint.TokenURL)
if err != nil {
return nil, err
}
@ -144,7 +143,7 @@ func (idp *BilibiliIdProvider) GetToken(code string) (*oauth2.Token, error) {
type BilibiliUserInfo struct {
Name string `json:"name"`
Face string `json:"face"`
OpenId string `json:"openid`
OpenId string `json:"openid"`
}
type BilibiliUserInfoResponse struct {
@ -167,12 +166,11 @@ func (idp *BilibiliIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
userInfoUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.AuthURL, params.Encode())
resp, err := idp.Client.Get(userInfoUrl)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -206,7 +204,7 @@ func (idp *BilibiliIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"time"
@ -71,8 +71,7 @@ func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -82,7 +81,7 @@ func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err
}
//check if token is expired
// check if token is expired
if pToken.ExpiresIn <= 0 {
return nil, fmt.Errorf("%s", pToken.AccessToken)
}
@ -91,7 +90,6 @@ func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
}
return token, nil
}
/*
@ -125,7 +123,7 @@ func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error
if err != nil {
return nil, err
}
//add accesstoken to bearer token
// add accesstoken to bearer token
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
resp, err := idp.Client.Do(request)
if err != nil {
@ -133,7 +131,7 @@ func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -155,5 +153,4 @@ func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error
AvatarUrl: cdUserinfo.AvatarUrl,
}
return userInfo, nil
}

View File

@ -18,7 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
_ "net/url"
_ "time"
@ -36,7 +36,7 @@ func NewCustomIdProvider(clientId string, clientSecret string, redirectUrl strin
idp := &CustomIdProvider{}
idp.UserInfoUrl = userInfoUrl
var config = &oauth2.Config{
config := &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
@ -76,7 +76,7 @@ func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
if err != nil {
return nil, err
}
//add accessToken to request header
// add accessToken to request header
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
resp, err := idp.Client.Do(request)
if err != nil {
@ -84,7 +84,7 @@ func (idp *CustomIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -48,12 +47,12 @@ func (idp *DingTalkIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *DingTalkIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
AuthURL: "https://api.dingtalk.com/v1.0/contact/users/me",
TokenURL: "https://api.dingtalk.com/v1.0/oauth2/userAccessToken",
}
var config = &oauth2.Config{
config := &oauth2.Config{
// DingTalk not allow to set scopes,here it is just a placeholder,
// convenient to use later
Scopes: []string{"", ""},
@ -101,7 +100,7 @@ func (idp *DingTalkIdProvider) GetToken(code string) (*oauth2.Token, error) {
token := &oauth2.Token{
AccessToken: pToken.AccessToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
Expiry: time.Unix(time.Now().Unix()+pToken.ExpiresIn, 0),
}
return token, nil
}
@ -145,7 +144,7 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -180,7 +179,7 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"time"
@ -42,12 +42,12 @@ func (idp *DouyinIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *DouyinIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://open.douyin.com/oauth/access_token",
AuthURL: "https://open.douyin.com/platform/oauth/connect",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"user_info"},
Endpoint: endpoint,
ClientID: clientId,
@ -98,7 +98,7 @@ func (idp *DouyinIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -177,7 +177,7 @@ func (idp *DouyinIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
defer resp.Body.Close()
respBody, err := ioutil.ReadAll(resp.Body)
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -46,11 +46,11 @@ func (idp *FacebookIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *FacebookIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://graph.facebook.com/oauth/access_token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"email,public_profile"},
Endpoint: endpoint,
ClientID: clientId,
@ -62,15 +62,16 @@ func (idp *FacebookIdProvider) getConfig(clientId string, clientSecret string, r
}
type FacebookAccessToken struct {
AccessToken string `json:"access_token"` //Interface call credentials
TokenType string `json:"token_type"` //Access token type
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
AccessToken string `json:"access_token"` // Interface call credentials
TokenType string `json:"token_type"` // Access token type
ExpiresIn int64 `json:"expires_in"` // access_token interface call credential timeout time, unit (seconds)
}
type FacebookCheckToken struct {
Data string `json:"data"`
}
// FacebookCheckTokenData
// Get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken
type FacebookCheckTokenData struct {
UserId string `json:"user_id"`

View File

@ -19,7 +19,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -49,11 +48,11 @@ func (idp *GiteeIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *GiteeIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://gitee.com/oauth/token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"user_info emails"},
Endpoint: endpoint,
@ -93,7 +92,7 @@ func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
rbs, err := ioutil.ReadAll(resp.Body)
rbs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
"strings"
@ -49,12 +48,12 @@ func (idp *GithubIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *GithubIdProvider) getConfig() *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
AuthURL: "https://github.com/login/oauth/authorize",
TokenURL: "https://github.com/login/oauth/access_token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"user:email", "read:user"},
Endpoint: endpoint,
}
@ -93,7 +92,6 @@ func (idp *GithubIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
return token, nil
}
//{
@ -203,7 +201,7 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -237,7 +235,7 @@ func (idp *GithubIdProvider) postWithBody(body interface{}, url string) ([]byte,
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"strconv"
@ -46,11 +46,11 @@ func (idp *GitlabIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *GitlabIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://gitlab.com/oauth/token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"read_user+profile"},
Endpoint: endpoint,
ClientID: clientId,
@ -85,7 +85,7 @@ func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -209,7 +209,7 @@ func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -19,7 +19,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"golang.org/x/oauth2"
@ -47,12 +47,12 @@ func (idp *GoogleIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *GoogleIdProvider) getConfig() *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
AuthURL: "https://accounts.google.com/o/oauth2/auth",
TokenURL: "https://accounts.google.com/o/oauth2/token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"profile", "email"},
Endpoint: endpoint,
}
@ -95,7 +95,7 @@ func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -207,7 +207,8 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
return &idp
}
//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
// SetHttpClient
// 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) {
idpClient := reflect.ValueOf(idp.Provider).Elem().FieldByName("HTTPClient")
idpClient.Set(reflect.ValueOf(client))
@ -225,8 +226,8 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err
}
} else {
//Need to construct variables supported by goth
//to call the function to obtain accessToken
// Need to construct variables supported by goth
// to call the function to obtain accessToken
value = url.Values{}
value.Add("code", code)
}
@ -235,7 +236,7 @@ func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err
}
//Get ExpiresAt's value
// Get ExpiresAt's value
valueOfExpire := reflect.ValueOf(idp.Session).Elem().FieldByName("ExpiresAt")
if valueOfExpire.IsValid() {
expireAt = valueOfExpire.Interface().(time.Time)
@ -264,8 +265,8 @@ func getUser(gothUser goth.User, provider string) *UserInfo {
Email: gothUser.Email,
AvatarUrl: gothUser.AvatarURL,
}
//Some idp return an empty Name
//so construct the Name with firstname and lastname or nickname
// Some idp return an empty Name
// so construct the Name with firstname and lastname or nickname
if user.Username == "" {
if gothUser.FirstName != "" && gothUser.LastName != "" {
user.Username = getName(gothUser.FirstName, gothUser.LastName)

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"golang.org/x/oauth2"
@ -43,7 +43,7 @@ func (idp *InfoflowInternalIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *InfoflowInternalIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var config = &oauth2.Config{
config := &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
@ -58,6 +58,7 @@ type InfoflowInterToken struct {
AccessToken string `json:"access_token"`
}
// GetToken
// get more detail via: https://qy.baidu.com/doc/index.html#/inner_quickstart/flow?id=%E8%8E%B7%E5%8F%96accesstoken
func (idp *InfoflowInternalIdProvider) GetToken(code string) (*oauth2.Token, error) {
pTokenParams := &struct {
@ -69,7 +70,7 @@ func (idp *InfoflowInternalIdProvider) GetToken(code string) (*oauth2.Token, err
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -137,9 +138,10 @@ type InfoflowInternalUserInfo struct {
Email string `json:"email"`
}
// GetUserInfo
// get more detail via: https://qy.baidu.com/doc/index.html#/inner_serverapi/contacts?id=%e8%8e%b7%e5%8f%96%e6%88%90%e5%91%98
func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
//Get userid first
// Get userid first
accessToken := token.AccessToken
code := token.Extra("code").(string)
resp, err := idp.Client.Get(fmt.Sprintf("https://qy.im.baidu.com/api/user/getuserinfo?access_token=%s&code=%s&agentid=%s", accessToken, code, idp.AgentId))
@ -147,7 +149,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -159,13 +161,13 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
if userResp.Errcode != 0 {
return nil, fmt.Errorf("userIdResp.Errcode = %d, userIdResp.Errmsg = %s", userResp.Errcode, userResp.Errmsg)
}
//Use userid and accesstoken to get user information
// Use userid and accesstoken to get user information
resp, err = idp.Client.Get(fmt.Sprintf("https://api.im.baidu.com/api/user/get?access_token=%s&userid=%s", accessToken, userResp.UserId))
if err != nil {
return nil, err
}
data, err = ioutil.ReadAll(resp.Body)
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -47,7 +46,7 @@ func (idp *InfoflowIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *InfoflowIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var config = &oauth2.Config{
config := &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
@ -63,6 +62,7 @@ type InfoflowToken struct {
ExpiresIn int `json:"expires_in"`
}
// GetToken
// get more detail via: https://qy.baidu.com/doc/index.html#/third_serverapi/authority
func (idp *InfoflowIdProvider) GetToken(code string) (*oauth2.Token, error) {
pTokenParams := &struct {
@ -134,9 +134,10 @@ type InfoflowUserInfo struct {
Email string `json:"email"`
}
// GetUserInfo
// get more detail via: https://qy.baidu.com/doc/index.html#/third_serverapi/contacts?id=%e8%8e%b7%e5%8f%96%e6%88%90%e5%91%98
func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
//Get userid first
// Get userid first
accessToken := token.AccessToken
code := token.Extra("code").(string)
resp, err := idp.Client.Get(fmt.Sprintf("https://api.im.baidu.com/api/user/getuserinfo?access_token=%s&code=%s&agentid=%s", accessToken, code, idp.AgentId))
@ -144,7 +145,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -156,13 +157,13 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
if userResp.Errcode != 0 {
return nil, fmt.Errorf("userIdResp.Errcode = %d, userIdResp.Errmsg = %s", userResp.Errcode, userResp.Errmsg)
}
//Use userid and accesstoken to get user information
// Use userid and accesstoken to get user information
resp, err = idp.Client.Get(fmt.Sprintf("https://api.im.baidu.com/api/user/get?access_token=%s&userid=%s", accessToken, userResp.UserId))
if err != nil {
return nil, err
}
data, err = ioutil.ReadAll(resp.Body)
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -197,7 +198,7 @@ func (idp *InfoflowIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,6 @@ package idp
import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -45,11 +44,11 @@ func (idp *LarkIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *LarkIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{},
Endpoint: endpoint,
ClientID: clientId,
@ -173,7 +172,7 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
return nil, err
}
defer resp.Body.Close()
data, err = ioutil.ReadAll(resp.Body)
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -204,7 +203,7 @@ func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, e
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
@ -47,11 +46,11 @@ func (idp *LinkedInIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *LinkedInIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://www.linkedIn.com/oauth/v2/accessToken",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"email,public_profile"},
Endpoint: endpoint,
ClientID: clientId,
@ -63,8 +62,8 @@ func (idp *LinkedInIdProvider) getConfig(clientId string, clientSecret string, r
}
type LinkedInAccessToken struct {
AccessToken string `json:"access_token"` //Interface call credentials
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
AccessToken string `json:"access_token"` // Interface call credentials
ExpiresIn int64 `json:"expires_in"` // access_token interface call credential timeout time, unit (seconds)
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
@ -85,7 +84,7 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
rbs, err := ioutil.ReadAll(resp.Body)
rbs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -323,7 +322,7 @@ func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([
}
}(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
bs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"time"
@ -48,12 +48,12 @@ func (idp *OktaIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *OktaIdProvider) getConfig(hostUrl string, clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: fmt.Sprintf("%s/v1/token", hostUrl),
AuthURL: fmt.Sprintf("%s/v1/authorize", hostUrl),
}
var config = &oauth2.Config{
config := &oauth2.Config{
// openid is required for authentication requests
// get more details via: https://developer.okta.com/docs/reference/api/oidc/#reserved-scopes
Scopes: []string{"openid", "profile", "email"},
@ -114,7 +114,7 @@ func (idp *OktaIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -178,7 +178,7 @@ func (idp *OktaIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"io"
"net/http"
"net/url"
"regexp"
@ -48,11 +48,11 @@ func (idp *QqIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *QqIdProvider) getConfig() *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://graph.qq.com/oauth2.0/token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"get_user_info"},
Endpoint: endpoint,
}
@ -75,7 +75,7 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
defer resp.Body.Close()
tokenContent, err := ioutil.ReadAll(resp.Body)
tokenContent, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -148,7 +148,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
openIdBody, err := ioutil.ReadAll(resp.Body)
openIdBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -167,7 +167,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
userInfoBody, err := ioutil.ReadAll(resp.Body)
userInfoBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -47,11 +47,11 @@ func (idp *WeChatIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *WeChatIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://graph.qq.com/oauth2.0/token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"snsapi_login"},
Endpoint: endpoint,
ClientID: clientId,
@ -63,12 +63,12 @@ func (idp *WeChatIdProvider) getConfig(clientId string, clientSecret string, red
}
type WechatAccessToken struct {
AccessToken string `json:"access_token"` //Interface call credentials
ExpiresIn int64 `json:"expires_in"` //access_token interface call credential timeout time, unit (seconds)
RefreshToken string `json:"refresh_token"` //User refresh access_token
Openid string `json:"openid"` //Unique ID of authorized user
Scope string `json:"scope"` //The scope of user authorization, separated by commas. (,)
Unionid string `json:"unionid"` //This field will appear if and only if the website application has been authorized by the user's UserInfo.
AccessToken string `json:"access_token"` // Interface call credentials
ExpiresIn int64 `json:"expires_in"` // access_token interface call credential timeout time, unit (seconds)
RefreshToken string `json:"refresh_token"` // User refresh access_token
Openid string `json:"openid"` // Unique ID of authorized user
Scope string `json:"scope"` // The scope of user authorization, separated by commas. (,)
Unionid string `json:"unionid"` // This field will appear if and only if the website application has been authorized by the user's UserInfo.
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
@ -144,7 +144,7 @@ type WechatUserInfo struct {
City string `json:"city"` // City filled in by general user's personal data
Province string `json:"province"` // Province filled in by ordinary user's personal information
Country string `json:"country"` // Country, such as China is CN
Headimgurl string `json:"headimgurl"` // User avatar, the last value represents the size of the square avatar (there are optional values of 0, 46, 64, 96, 132, 0 represents a 640*640 square avatar), this item is empty when the user does not have a avatar
Headimgurl string `json:"headimgurl"` // User avatar, the last value represents the size of the square avatar (there are optional values of 0, 46, 64, 96, 132, 0 represents a 640*640 square avatar), this item is empty when the user does not have an avatar
Privilege []string `json:"privilege"` // User Privilege information, json array, such as Wechat Woka user (chinaunicom)
Unionid string `json:"unionid"` // Unified user identification. For an application under a WeChat open platform account, the unionid of the same user is unique.
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"golang.org/x/oauth2"
@ -42,7 +42,7 @@ func (idp *WeChatMiniProgramIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *WeChatMiniProgramIdProvider) getConfig(clientId string, clientSecret string) *oauth2.Config {
var config = &oauth2.Config{
config := &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
}
@ -65,7 +65,7 @@ func (idp *WeChatMiniProgramIdProvider) GetSessionByCode(code string) (*WeChatMi
return nil, err
}
defer sessionResponse.Body.Close()
data, err := ioutil.ReadAll(sessionResponse.Body)
data, err := io.ReadAll(sessionResponse.Body)
if err != nil {
return nil, err
}
@ -78,5 +78,4 @@ func (idp *WeChatMiniProgramIdProvider) GetSessionByCode(code string) (*WeChatMi
return nil, fmt.Errorf("err: %s", session.Errmsg)
}
return &session, nil
}

View File

@ -17,14 +17,15 @@ package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"io"
"net/http"
"time"
"golang.org/x/oauth2"
)
//This idp is using wecom internal application api as idp
// WeComInternalIdProvider
// This idp is using wecom internal application api as idp
type WeComInternalIdProvider struct {
Client *http.Client
Config *oauth2.Config
@ -44,7 +45,7 @@ func (idp *WeComInternalIdProvider) SetHttpClient(client *http.Client) {
}
func (idp *WeComInternalIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var config = &oauth2.Config{
config := &oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
RedirectURL: redirectUrl,
@ -72,7 +73,7 @@ func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error)
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -115,7 +116,7 @@ type WecomInternalUserInfo struct {
}
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
//Get userid first
// Get userid first
accessToken := token.AccessToken
code := token.Extra("code").(string)
resp, err := idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s", accessToken, code))
@ -123,7 +124,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -138,13 +139,13 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
if userResp.OpenId != "" {
return nil, fmt.Errorf("not an internal user")
}
//Use userid and accesstoken to get user information
// Use userid and accesstoken to get user information
resp, err = idp.Client.Get(fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/get?access_token=%s&userid=%s", accessToken, userResp.UserId))
if err != nil {
return nil, err
}
data, err = ioutil.ReadAll(resp.Body)
data, err = io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -46,11 +45,11 @@ func (idp *WeComIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *WeComIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://graph.qq.com/oauth2.0/token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{"snsapi_login"},
Endpoint: endpoint,
ClientID: clientId,
@ -195,7 +194,7 @@ func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte,
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -19,7 +19,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -48,11 +47,11 @@ func (idp *WeiBoIdProvider) SetHttpClient(client *http.Client) {
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
func (idp *WeiBoIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
var endpoint = oauth2.Endpoint{
endpoint := oauth2.Endpoint{
TokenURL: "https://api.weibo.com/oauth2/access_token",
}
var config = &oauth2.Config{
config := &oauth2.Config{
Scopes: []string{""},
Endpoint: endpoint,
ClientID: clientId,
@ -92,7 +91,7 @@ func (idp *WeiBoIdProvider) GetToken(code string) (*oauth2.Token, error) {
return
}
}(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
bs, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -139,7 +139,7 @@
"cryptoAlgorithm": "RS256",
"bitSize": 4096,
"expireInYears": 20,
"publicKey": "",
"certificate": "",
"privateKey": ""
}
],

View File

@ -42,9 +42,9 @@ func main() {
proxy.InitHttpClient()
authz.InitAuthz()
util.SafeGoroutine(func() {object.RunSyncUsersJob()})
util.SafeGoroutine(func() { object.RunSyncUsersJob() })
//beego.DelStaticPath("/static")
// beego.DelStaticPath("/static")
beego.SetStaticPath("/static", "web/build/static")
beego.BConfig.WebConfig.DirectoryIndex = true
beego.SetStaticPath("/swagger", "swagger")
@ -66,14 +66,14 @@ func main() {
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
}
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
// beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode
err := logs.SetLogger("file", `{"filename":"logs/casdoor.log","maxdays":99999,"perm":"0770"}`)
if err != nil {
panic(err)
}
port := beego.AppConfig.DefaultInt("httpport", 8000)
//logs.SetLevel(logs.LevelInformational)
// logs.SetLevel(logs.LevelInformational)
logs.SetLogFuncCall(false)
beego.Run(fmt.Sprintf(":%v", port))
}

View File

@ -21,9 +21,10 @@ import (
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
//_ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql
//_ "github.com/lib/pq" // db = postgres
_ "github.com/denisenkom/go-mssqldb" // db = mssql
_ "github.com/go-sql-driver/mysql" // db = mysql
_ "github.com/lib/pq" // db = postgres
//_ "github.com/mattn/go-sqlite3" // db = sqlite3
"xorm.io/core"
"xorm.io/xorm"
)
@ -203,6 +204,11 @@ func (a *Adapter) createTable() {
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(PermissionRule))
if err != nil {
panic(err)
}
}
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {

View File

@ -47,6 +47,7 @@ type Application struct {
EnableSigninSession bool `json:"enableSigninSession"`
EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"`
EnableWebAuthn bool `json:"enableWebAuthn"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
@ -316,7 +317,7 @@ func (application *Application) GetId() string {
}
func CheckRedirectUriValid(application *Application, redirectUri string) bool {
var validUri = false
validUri := false
for _, tmpUri := range application.RedirectUris {
if strings.Contains(redirectUri, tmpUri) {
validUri = true

View File

@ -33,7 +33,7 @@ type Cert struct {
BitSize int `json:"bitSize"`
ExpireInYears int `json:"expireInYears"`
PublicKey string `xorm:"mediumtext" json:"publicKey"`
Certificate string `xorm:"mediumtext" json:"certificate"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
@ -123,9 +123,9 @@ func UpdateCert(id string, cert *Cert) bool {
}
func AddCert(cert *Cert) bool {
if cert.PublicKey == "" || cert.PrivateKey == "" {
publicKey, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
cert.PublicKey = publicKey
if cert.Certificate == "" || cert.PrivateKey == "" {
certificate, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
cert.Certificate = certificate
cert.PrivateKey = privateKey
}

View File

@ -182,7 +182,7 @@ func CheckUserPassword(organization string, username string, password string) (*
}
if user.Ldap != "" {
//ONLY for ldap users
// ONLY for ldap users
return checkLdapUserPassword(user, password)
} else {
msg := CheckPassword(user, password)
@ -197,14 +197,18 @@ func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
}
func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error) {
func CheckUserPermission(requestUserId, userId, userOwner string, strict bool) (bool, error) {
if requestUserId == "" {
return false, fmt.Errorf("please login first")
}
targetUser := GetUser(userId)
if targetUser == nil {
return false, fmt.Errorf("the user: %s doesn't exist", userId)
if userId != "" {
targetUser := GetUser(userId)
if targetUser == nil {
return false, fmt.Errorf("the user: %s doesn't exist", userId)
}
userOwner = targetUser.Owner
}
hasPermission := false
@ -219,7 +223,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if targetUser.Owner == requestUser.Owner {
} else if userOwner == requestUser.Owner {
if strict {
hasPermission = requestUser.IsAdmin
} else {
@ -229,4 +233,30 @@ func CheckUserPermission(requestUserId, userId string, strict bool) (bool, error
}
return hasPermission, fmt.Errorf("you don't have the permission to do this")
}
}
func CheckAccessPermission(userId string, application *Application) (bool, error) {
permissions := GetPermissions(application.Organization)
allowed := true
var err error
for _, permission := range permissions {
if !permission.IsEnabled || len(permission.Users) == 0 {
continue
}
isHit := false
for _, resource := range permission.Resources {
if application.Name == resource {
isHit = true
break
}
}
if isHit {
enforcer := getEnforcer(permission)
allowed, err = enforcer.Enforce(userId, application.Name, "read")
break
}
}
return allowed, err
}

View File

@ -33,6 +33,7 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
// DailSmtpServer Dail Smtp server
func DailSmtpServer(provider *Provider) error {
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
dialer.SSL = !provider.DisableSsl
sender, err := dialer.Dial()
if err != nil {

View File

@ -15,20 +15,25 @@
package object
import (
"io/ioutil"
"encoding/gob"
"os"
"github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/webauthn"
)
func InitDb() {
existed := initBuiltInOrganization()
if !existed {
initBuiltInPermission()
initBuiltInProvider()
initBuiltInUser()
initBuiltInApplication()
initBuiltInCert()
initBuiltInLdap()
}
initWebAuthn()
}
func initBuiltInOrganization() bool {
@ -66,12 +71,15 @@ func initBuiltInOrganization() bool {
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is global admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is forbidden", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is deleted", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "WebAuthn credentials", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
},
}
AddOrganization(organization)
@ -150,11 +158,11 @@ func initBuiltInApplication() {
func readTokenFromFile() (string, string) {
pemPath := "./object/token_jwt_key.pem"
keyPath := "./object/token_jwt_key.key"
pem, err := ioutil.ReadFile(pemPath)
pem, err := os.ReadFile(pemPath)
if err != nil {
return "", ""
}
key, err := ioutil.ReadFile(keyPath)
key, err := os.ReadFile(keyPath)
if err != nil {
return "", ""
}
@ -162,7 +170,7 @@ func readTokenFromFile() (string, string) {
}
func initBuiltInCert() {
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile()
tokenJwtCertificate, tokenJwtPrivateKey := readTokenFromFile()
cert := getCert("admin", "cert-built-in")
if cert != nil {
return
@ -178,7 +186,7 @@ func initBuiltInCert() {
CryptoAlgorithm: "RS256",
BitSize: 4096,
ExpireInYears: 20,
PublicKey: tokenJwtPublicKey,
Certificate: tokenJwtCertificate,
PrivateKey: tokenJwtPrivateKey,
}
AddCert(cert)
@ -221,3 +229,29 @@ func initBuiltInProvider() {
}
AddProvider(provider)
}
func initWebAuthn() {
gob.Register(webauthn.SessionData{})
}
func initBuiltInPermission() {
permission := GetPermission("built-in/permission-built-in")
if permission != nil {
return
}
permission = &Permission{
Owner: "built-in",
Name: "permission-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Built-in Permission",
Users: []string{"built-in/admin"},
Roles: []string{},
ResourceType: "Application",
Resources: []string{"app-built-in"},
Actions: []string{"Read", "Write", "Admin"},
Effect: "Allow",
IsEnabled: true,
}
AddPermission(permission)
}

View File

@ -89,6 +89,8 @@ func initDefinedOrganization(organization *Organization) {
{Name: "Bio", Visible: true, ViewRule: "Public", ModifyRule: "Self"},
{Name: "Tag", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Signup application", Visible: true, ViewRule: "Public", ModifyRule: "Admin"},
{Name: "Roles", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "Permissions", Visible: true, ViewRule: "Public", ModifyRule: "Immutable"},
{Name: "3rd-party logins", Visible: true, ViewRule: "Self", ModifyRule: "Self"},
{Name: "Properties", Visible: false, ViewRule: "Admin", ModifyRule: "Admin"},
{Name: "Is admin", Visible: true, ViewRule: "Admin", ModifyRule: "Admin"},

View File

@ -56,7 +56,7 @@ type ldapUser struct {
Uid string
Cn string
GidNumber string
//Gcn string
// Gcn string
Uuid string
Mail string
Email string
@ -73,7 +73,7 @@ type LdapRespUser struct {
Uid string `json:"uid"`
Cn string `json:"cn"`
GroupId string `json:"groupId"`
//GroupName string `json:"groupName"`
// GroupName string `json:"groupName"`
Uuid string `json:"uuid"`
Email string `json:"email"`
Phone string `json:"phone"`
@ -208,11 +208,15 @@ func GetLdapConn(host string, port int, adminUser string, adminPasswd string) (*
func (l *ldapConn) GetLdapUsers(baseDn string) ([]ldapUser, error) {
SearchFilter := "(objectClass=posixAccount)"
SearchAttributes := []string{"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
SearchAttributes := []string{
"uidNumber", "uid", "cn", "gidNumber", "entryUUID", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
}
SearchFilterMsAD := "(objectClass=user)"
SearchAttributesMsAD := []string{"uidNumber", "sAMAccountName", "cn", "gidNumber", "entryUUID", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress"}
SearchAttributesMsAD := []string{
"uidNumber", "sAMAccountName", "cn", "gidNumber", "entryUUID", "mail", "email",
"emailAddress", "telephoneNumber", "mobile", "mobileTelephoneNumber", "registeredAddress", "postalAddress",
}
var searchReq *goldap.SearchRequest
if l.IsAD {
searchReq = goldap.NewSearchRequest(baseDn,
@ -459,7 +463,7 @@ func CheckLdapUuidExist(owner string, uuids []string) []string {
}
}
for uuid, _ := range existUuidSet {
for uuid := range existUuidSet {
existUuids = append(existUuids, uuid)
}
return existUuids

View File

@ -31,7 +31,8 @@ func GetLdapAutoSynchronizer() *LdapAutoSynchronizer {
return globalLdapAutoSynchronizer
}
//start autosync for specified ldap, old existing autosync goroutine will be ceased
// StartAutoSync
// start autosync for specified ldap, old existing autosync goroutine will be ceased
func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
l.Lock()
defer l.Unlock()
@ -48,7 +49,7 @@ func (l *LdapAutoSynchronizer) StartAutoSync(ldapId string) error {
stopChan := make(chan struct{})
l.ldapIdToStopChan[ldapId] = stopChan
logs.Info(fmt.Sprintf("autoSync started for %s", ldap.Id))
util.SafeGoroutine(func() {l.syncRoutine(ldap, stopChan)})
util.SafeGoroutine(func() { l.syncRoutine(ldap, stopChan) })
return nil
}
@ -61,7 +62,7 @@ func (l *LdapAutoSynchronizer) StopAutoSync(ldapId string) {
}
}
//autosync goroutine
// autosync goroutine
func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
ticker := time.NewTicker(time.Duration(ldap.AutoSync) * time.Minute)
defer ticker.Stop()
@ -74,7 +75,7 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
}
UpdateLdapSyncTime(ldap.Id)
//fetch all users
// fetch all users
conn, err := GetLdapConn(ldap.Host, ldap.Port, ldap.Admin, ldap.Passwd)
if err != nil {
logs.Warning(fmt.Sprintf("autoSync failed for %s, error %s", ldap.Id, err))
@ -93,10 +94,10 @@ func (l *LdapAutoSynchronizer) syncRoutine(ldap *Ldap, stopChan chan struct{}) {
logs.Info(fmt.Sprintf("ldap autosync success, %d new users, %d existing users", len(users)-len(*existed), len(*existed)))
}
}
}
//start all autosync goroutine for existing ldap servers in each organizations
// LdapAutoSynchronizerStartUpAll
// start all autosync goroutine for existing ldap servers in each organizations
func (l *LdapAutoSynchronizer) LdapAutoSynchronizerStartUpAll() {
organizations := []*Organization{}
err := adapter.Engine.Desc("created_time").Find(&organizations)

View File

@ -27,8 +27,8 @@ type Model struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
ModelText string `xorm:"mediumtext" json:"modelText"`
IsEnabled bool `json:"isEnabled"`
ModelText string `xorm:"mediumtext" json:"modelText"`
IsEnabled bool `json:"isEnabled"`
}
func GetModelCount(owner, field, value string) int {

View File

@ -93,11 +93,11 @@ func GetOidcDiscovery(host string) OidcDiscovery {
func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
certs := GetCerts("admin")
jwks := jose.JSONWebKeySet{}
//follows the protocol rfc 7517(draft)
//link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
//or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
// follows the protocol rfc 7517(draft)
// link here: https://self-issued.info/docs/draft-ietf-jose-json-web-key.html
// or https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key
for _, cert := range certs {
certPemBlock := []byte(cert.PublicKey)
certPemBlock := []byte(cert.Certificate)
certDerBlock, _ := pem.Decode(certPemBlock)
x509Cert, _ := x509.ParseCertificate(certDerBlock.Bytes)

View File

@ -15,6 +15,8 @@
package object
import (
"fmt"
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
@ -186,3 +188,31 @@ func DeleteOrganization(organization *Organization) bool {
func GetOrganizationByUser(user *User) *Organization {
return getOrganization("admin", user.Owner)
}
func GetAccountItemByName(name string, organization *Organization) *AccountItem {
if organization == nil {
return nil
}
for _, accountItem := range organization.AccountItems {
if accountItem.Name == name {
return accountItem
}
}
return nil
}
func CheckAccountItemModifyRule(accountItem *AccountItem, user *User) (bool, string) {
switch accountItem.ModifyRule {
case "Admin":
if !(user.IsAdmin || user.IsGlobalAdmin) {
return false, fmt.Sprintf("Only admin can modify the %s.", accountItem.Name)
}
case "Immutable":
return false, fmt.Sprintf("The %s is immutable.", accountItem.Name)
case "Self":
break
default:
return false, fmt.Sprintf("Unknown modify rule %s.", accountItem.ModifyRule)
}
return true, ""
}

View File

@ -39,6 +39,16 @@ type Permission struct {
IsEnabled bool `json:"isEnabled"`
}
type PermissionRule struct {
Ptype string `xorm:"varchar(100) index not null default ''" json:"ptype"`
V0 string `xorm:"varchar(100) index not null default ''" json:"v0"`
V1 string `xorm:"varchar(100) index not null default ''" json:"v1"`
V2 string `xorm:"varchar(100) index not null default ''" json:"v2"`
V3 string `xorm:"varchar(100) index not null default ''" json:"v3"`
V4 string `xorm:"varchar(100) index not null default ''" json:"v4"`
V5 string `xorm:"varchar(100) index not null default ''" json:"v5"`
}
func GetPermissionCount(owner, field, value string) int {
session := GetSession(owner, -1, -1, field, value, "", "")
count, err := session.Count(&Permission{})
@ -95,7 +105,8 @@ func GetPermission(id string) *Permission {
func UpdatePermission(id string, permission *Permission) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getPermission(owner, name) == nil {
oldPermission := getPermission(owner, name)
if oldPermission == nil {
return false
}
@ -104,6 +115,11 @@ func UpdatePermission(id string, permission *Permission) bool {
panic(err)
}
if affected != 0 {
removePolicies(oldPermission)
addPolicies(permission)
}
return affected != 0
}
@ -113,6 +129,10 @@ func AddPermission(permission *Permission) bool {
panic(err)
}
if affected != 0 {
addPolicies(permission)
}
return affected != 0
}
@ -122,9 +142,23 @@ func DeletePermission(permission *Permission) bool {
panic(err)
}
if affected != 0 {
removePolicies(permission)
}
return affected != 0
}
func (permission *Permission) GetId() string {
return fmt.Sprintf("%s/%s", permission.Owner, permission.Name)
}
func GetPermissionsByUser(userId string) []*Permission {
permissions := []*Permission{}
err := adapter.Engine.Where("users like ?", "%"+userId+"%").Find(&permissions)
if err != nil {
panic(err)
}
return permissions
}

View File

@ -0,0 +1,162 @@
// Copyright 2021 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 (
"strings"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2"
"github.com/casdoor/casdoor/conf"
)
func getEnforcer(permission *Permission) *casbin.Enforcer {
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
adapter, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "permission_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}
modelText := `
[request_definition]
r = sub, obj, act
[policy_definition]
p = permission, sub, obj, act
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act`
permissionModel := getModel(permission.Owner, permission.Model)
if permissionModel != nil {
modelText = permissionModel.ModelText
}
m, err := model.NewModelFromString(modelText)
if err != nil {
panic(err)
}
enforcer, err := casbin.NewEnforcer(m, adapter)
if err != nil {
panic(err)
}
err = enforcer.LoadFilteredPolicy(xormadapter.Filter{V0: []string{permission.GetId()}})
if err != nil {
panic(err)
}
return enforcer
}
func getPolicies(permission *Permission) [][]string {
var policies [][]string
for _, user := range permission.Users {
for _, resource := range permission.Resources {
for _, action := range permission.Actions {
policies = append(policies, []string{permission.GetId(), user, resource, strings.ToLower(action)})
}
}
}
for _, role := range permission.Roles {
for _, resource := range permission.Resources {
for _, action := range permission.Actions {
policies = append(policies, []string{permission.GetId(), role, resource, strings.ToLower(action)})
}
}
}
return policies
}
func addPolicies(permission *Permission) {
enforcer := getEnforcer(permission)
policies := getPolicies(permission)
_, err := enforcer.AddPolicies(policies)
if err != nil {
panic(err)
}
}
func removePolicies(permission *Permission) {
enforcer := getEnforcer(permission)
_, err := enforcer.RemoveFilteredPolicy(0, permission.GetId())
if err != nil {
panic(err)
}
}
func Enforce(userId string, permissionRule *PermissionRule) bool {
permission := GetPermission(permissionRule.V0)
enforcer := getEnforcer(permission)
allow, err := enforcer.Enforce(userId, permissionRule.V2, permissionRule.V3)
if err != nil {
panic(err)
}
return allow
}
func BatchEnforce(userId string, permissionRules []PermissionRule) []bool {
var requests [][]interface{}
for _, permissionRule := range permissionRules {
requests = append(requests, []interface{}{userId, permissionRule.V2, permissionRule.V3})
}
permission := GetPermission(permissionRules[0].V0)
enforcer := getEnforcer(permission)
allow, err := enforcer.BatchEnforce(requests)
if err != nil {
panic(err)
}
return allow
}
func getAllValues(userId string, sec string, fieldIndex int) []string {
permissions := GetPermissionsByUser(userId)
var values []string
for _, permission := range permissions {
enforcer := getEnforcer(permission)
enforcer.ClearPolicy()
err := enforcer.LoadFilteredPolicy(xormadapter.Filter{V0: []string{permission.GetId()}, V1: []string{userId}})
if err != nil {
return nil
}
for _, value := range enforcer.GetModel().GetValuesForFieldInPolicyAllTypes(sec, fieldIndex) {
values = append(values, value)
}
}
return values
}
func GetAllObjects(userId string) []string {
return getAllValues(userId, "p", 2)
}
func GetAllActions(userId string) []string {
return getAllValues(userId, "p", 3)
}
func GetAllRoles(userId string) []string {
roles := GetRolesByUser(userId)
var res []string
for _, role := range roles {
res = append(res, role.Name)
}
return res
}

View File

@ -38,6 +38,8 @@ type Product struct {
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"`
ProviderObjs []*Provider `xorm:"-" json:"providerObjs"`
}
func GetProductCount(owner, field, value string) int {
@ -209,3 +211,14 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
return payUrl, err
}
func ExtendProductWithProviders(product *Product) {
product.ProviderObjs = []*Provider{}
m := getProviderMap(product.Owner)
for _, providerItem := range product.Providers {
if provider, ok := m[providerItem]; ok {
product.ProviderObjs = append(product.ProviderObjs, provider)
}
}
}

View File

@ -30,7 +30,7 @@ func TestProduct(t *testing.T) {
product := GetProduct("admin/product_123")
provider := getProvider(product.Owner, "provider_pay_alipay")
cert := getCert(product.Owner, "cert-pay-alipay")
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
paymentName := util.GenerateTimeId()
returnUrl := ""

View File

@ -43,10 +43,11 @@ type Provider struct {
CustomUserInfoUrl string `xorm:"varchar(200)" json:"customUserInfoUrl"`
CustomLogo string `xorm:"varchar(200)" json:"customLogo"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
Title string `xorm:"varchar(100)" json:"title"`
Content string `xorm:"varchar(1000)" json:"content"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
DisableSsl bool `json:"disableSsl"`
Title string `xorm:"varchar(100)" json:"title"`
Content string `xorm:"varchar(1000)" json:"content"`
RegionId string `xorm:"varchar(100)" json:"regionId"`
SignName string `xorm:"varchar(100)" json:"signName"`
@ -214,7 +215,7 @@ func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
}
}
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.Certificate, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
if pProvider == nil {
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
}

View File

@ -33,6 +33,15 @@ func (application *Application) GetProviderItem(providerName string) *ProviderIt
return nil
}
func (application *Application) GetProviderItemByType(providerType string) *ProviderItem {
for _, item := range application.Providers {
if item.Provider.Type == providerType {
return item
}
}
return nil
}
func (pi *ProviderItem) IsProviderVisible() bool {
if pi.Provider == nil {
return false

View File

@ -29,7 +29,7 @@ func init() {
var err error
logPostOnly, err = conf.GetConfigBool("logPostOnly")
if err != nil {
//panic(err)
// panic(err)
}
}

View File

@ -110,7 +110,7 @@ func UpdateResource(id string, resource *Resource) bool {
panic(err)
}
//return affected != 0
// return affected != 0
return true
}

View File

@ -121,3 +121,13 @@ func DeleteRole(role *Role) bool {
func (role *Role) GetId() string {
return fmt.Sprintf("%s/%s", role.Owner, role.Name)
}
func GetRolesByUser(userId string) []*Role {
roles := []*Role{}
err := adapter.Engine.Where("users like ?", "%"+userId+"%").Find(&roles)
if err != nil {
panic(err)
}
return roles
}

View File

@ -35,8 +35,9 @@ import (
uuid "github.com/satori/go.uuid"
)
//returns a saml2 response
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
// NewSamlResponse
// returns a saml2 response
func NewSamlResponse(user *User, host string, certificate string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
samlResponse := &etree.Element{
Space: "samlp",
Tag: "Response",
@ -100,7 +101,6 @@ func NewSamlResponse(user *User, host string, publicKey string, destination stri
displayName.CreateElement("saml:AttributeValue").CreateAttr("xsi:type", "xs:string").Element().SetText(user.DisplayName)
return samlResponse, nil
}
type X509Key struct {
@ -114,7 +114,8 @@ func (x X509Key) GetKeyPair() (privateKey *rsa.PrivateKey, cert []byte, err erro
return privateKey, cert, err
}
//SAML METADATA
// IdpEntityDescriptor
// SAML METADATA
type IdpEntityDescriptor struct {
XMLName xml.Name `xml:"EntityDescriptor"`
DS string `xml:"xmlns:ds,attr"`
@ -177,8 +178,8 @@ type Attribute struct {
func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, error) {
//_, originBackend := getOriginFromHost(host)
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
block, _ := pem.Decode([]byte(cert.Certificate))
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
origin := beego.AppConfig.String("origin")
originFrontend, originBackend := getOriginFromHost(host)
@ -199,7 +200,7 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
KeyInfo: KeyInfo{
X509Data: X509Data{
X509Certificate: X509Certificate{
Cert: publicKey,
Cert: certificate,
},
},
},
@ -248,18 +249,18 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
return "", "", fmt.Errorf("err: invalid issuer url")
}
// get public key string
// get certificate string
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
block, _ := pem.Decode([]byte(cert.Certificate))
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
_, originBackend := getOriginFromHost(host)
// build signedResponse
samlResponse, _ := NewSamlResponse(user, originBackend, publicKey, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
samlResponse, _ := NewSamlResponse(user, originBackend, certificate, authnRequest.AssertionConsumerServiceURL, authnRequest.Issuer.Url, authnRequest.ID, application.RedirectUris)
randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey,
X509Certificate: publicKey,
X509Certificate: certificate,
}
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
ctx.Hash = crypto.SHA1
@ -299,7 +300,7 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
Space: "samlp",
Tag: "Response",
}
//create samlresponse
// create samlresponse
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:1.0:protocol")
samlResponse.CreateAttr("MajorVersion", "1")
samlResponse.CreateAttr("MinorVersion", "1")
@ -315,7 +316,7 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
samlResponse.CreateElement("samlp:Status").CreateElement("samlp:StatusCode").CreateAttr("Value", "samlp:Success")
//create assertion which is inside the response
// create assertion which is inside the response
assertion := samlResponse.CreateElement("saml:Assertion")
assertion.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:1.0:assertion")
assertion.CreateAttr("MajorVersion", "1")
@ -328,19 +329,19 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
condition.CreateAttr("NotBefore", now)
condition.CreateAttr("NotOnOrAfter", expireTime)
//AuthenticationStatement inside assertion
// AuthenticationStatement inside assertion
authenticationStatement := assertion.CreateElement("saml:AuthenticationStatement")
authenticationStatement.CreateAttr("AuthenticationMethod", "urn:oasis:names:tc:SAML:1.0:am:password")
authenticationStatement.CreateAttr("AuthenticationInstant", now)
//subject inside AuthenticationStatement
// subject inside AuthenticationStatement
subject := assertion.CreateElement("saml:Subject")
//nameIdentifier inside subject
// nameIdentifier inside subject
nameIdentifier := subject.CreateElement("saml:NameIdentifier")
//nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
// nameIdentifier.CreateAttr("Format", "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress")
nameIdentifier.SetText(user.Name)
//subjectConfirmation inside subject
// subjectConfirmation inside subject
subjectConfirmation := subject.CreateElement("saml:SubjectConfirmation")
subjectConfirmation.CreateElement("saml:ConfirmationMethod").SetText("urn:oasis:names:tc:SAML:1.0:cm:artifact")

View File

@ -44,7 +44,7 @@ func ParseSamlResponse(samlResponse string, providerType string) (string, error)
func GenerateSamlLoginUrl(id, relayState string) (string, string, error) {
provider := GetProvider(id)
if provider.Category != "SAML" {
return "", "", fmt.Errorf("Provider %s's category is not SAML", provider.Name)
return "", "", fmt.Errorf("provider %s's category is not SAML", provider.Name)
}
sp, err := buildSp(provider, "")
if err != nil {

View File

@ -17,6 +17,7 @@ package object
import (
"bytes"
"fmt"
"net/url"
"strings"
"github.com/casdoor/casdoor/conf"
@ -30,7 +31,7 @@ func init() {
var err error
isCloudIntranet, err = conf.GetConfigBool("isCloudIntranet")
if err != nil {
//panic(err)
// panic(err)
}
}
@ -42,8 +43,19 @@ func getProviderEndpoint(provider *Provider) string {
return endpoint
}
func escapePath(path string) string {
tokens := strings.Split(path, "/")
if len(tokens) > 0 {
tokens[len(tokens)-1] = url.QueryEscape(tokens[len(tokens)-1])
}
res := strings.Join(tokens, "/")
return res
}
func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath)
escapedPath := escapePath(fullFilePath)
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
host := ""
if provider.Type != "Local File System" {
@ -60,9 +72,9 @@ func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
host = fmt.Sprintf("%s/%s", host, provider.Bucket)
}
fileUrl := util.UrlJoin(host, objectKey)
fileUrl := util.UrlJoin(host, escapePath(objectKey))
if hasTimestamp {
fileUrl = fmt.Sprintf("%s?t=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime())
fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
}
return fileUrl, objectKey

View File

@ -22,7 +22,7 @@ import (
func (syncer *Syncer) syncUsers() {
fmt.Printf("Running syncUsers()..\n")
users, userMap := syncer.getUserMap()
users, userMap, userNameMap := syncer.getUserMap()
oUsers, oUserMap, err := syncer.getOriginalUserMap()
if err != nil {
fmt.Printf(err.Error())
@ -44,9 +44,11 @@ func (syncer *Syncer) syncUsers() {
for _, oUser := range oUsers {
id := oUser.Id
if _, ok := userMap[id]; !ok {
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
if _, ok := userNameMap[oUser.Name]; !ok {
newUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
fmt.Printf("New user: %v\n", newUser)
newUsers = append(newUsers, newUser)
}
} else {
user := userMap[id]
oHash := syncer.calculateHash(oUser)

View File

@ -151,6 +151,8 @@ func (syncer *Syncer) initAdapter() {
var dataSourceName string
if syncer.DatabaseType == "mssql" {
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
} else if syncer.DatabaseType == "postgres" {
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
} else {
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
}

View File

@ -173,16 +173,21 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
}
for _, tableColumn := range syncer.TableColumns {
tableColumnName := tableColumn.Name
if syncer.Type == "Keycloak" && syncer.DatabaseType == "postgres" {
tableColumnName = strings.ToLower(tableColumnName)
}
value := ""
if strings.Contains(tableColumn.Name, "+") {
names := strings.Split(tableColumn.Name, "+")
if strings.Contains(tableColumnName, "+") {
names := strings.Split(tableColumnName, "+")
var values []string
for _, name := range names {
values = append(values, result[strings.Trim(name, " ")])
}
value = strings.Join(values, " ")
} else {
value = result[tableColumn.Name]
value = result[tableColumnName]
}
syncer.setUserByKeyValue(originalUser, tableColumn.CasdoorName, value)
}
@ -198,7 +203,7 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
originalUser.PasswordSalt = credential.Salt
}
// query and set signup application from user group table
sql = fmt.Sprintf("select name from keycloak_group where id = " +
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 {
@ -209,7 +214,12 @@ func (syncer *Syncer) getOriginalUsersFromMap(results []map[string]string) []*Or
tm := time.Unix(i/int64(1000), 0)
originalUser.CreatedTime = tm.Format("2006-01-02T15:04:05+08:00")
// enable
originalUser.IsForbidden = !(result["ENABLED"] == "\x01")
value, ok := result["ENABLED"]
if ok {
originalUser.IsForbidden = !util.ParseBool(value)
} else {
originalUser.IsForbidden = !util.ParseBool(result["enabled"])
}
}
users = append(users, originalUser)

View File

@ -19,12 +19,15 @@ func (syncer *Syncer) getUsers() []*User {
return users
}
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User) {
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User, map[string]*User) {
users := syncer.getUsers()
m := map[string]*User{}
m1 := map[string]*User{}
m2 := map[string]*User{}
for _, user := range users {
m[user.Id] = user
m1[user.Id] = user
m2[user.Name] = user
}
return users, m
return users, m1, m2
}

View File

@ -27,14 +27,14 @@ import (
)
const (
hourSeconds = 3600
INVALID_REQUEST = "invalid_request"
INVALID_CLIENT = "invalid_client"
INVALID_GRANT = "invalid_grant"
UNAUTHORIZED_CLIENT = "unauthorized_client"
UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type"
INVALID_SCOPE = "invalid_scope"
ENDPOINT_ERROR = "endpoint_error"
hourSeconds = 3600
InvalidRequest = "invalid_request"
InvalidClient = "invalid_client"
InvalidGrant = "invalid_grant"
UnauthorizedClient = "unauthorized_client"
UnsupportedGrantType = "unsupported_grant_type"
InvalidScope = "invalid_scope"
EndpointError = "endpoint_error"
)
type Code struct {
@ -200,7 +200,7 @@ func DeleteToken(token *Token) bool {
return affected != 0
}
func DeleteTokenByAceessToken(accessToken string) (bool, *Application) {
func DeleteTokenByAccessToken(accessToken string) (bool, *Application) {
token := Token{AccessToken: accessToken}
existed, err := adapter.Engine.Get(&token)
if err != nil {
@ -220,7 +220,7 @@ func DeleteTokenByAceessToken(accessToken string) (bool, *Application) {
}
func GetTokenByAccessToken(accessToken string) *Token {
//Check if the accessToken is in the database
// Check if the accessToken is in the database
token := Token{AccessToken: accessToken}
existed, err := adapter.Engine.Get(&token)
if err != nil || !existed {
@ -325,16 +325,16 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
application := GetApplicationByClientId(clientId)
if application == nil {
return &TokenError{
Error: INVALID_CLIENT,
Error: InvalidClient,
ErrorDescription: "client_id is invalid",
}
}
//Check if grantType is allowed in the current application
// Check if grantType is allowed in the current application
if !IsGrantTypeValid(grantType, application.GrantTypes) && tag == "" {
return &TokenError{
Error: UNSUPPORTED_GRANT_TYPE,
Error: UnsupportedGrantType,
ErrorDescription: fmt.Sprintf("grant_type: %s is not supported in this application", grantType),
}
}
@ -377,20 +377,20 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
// check parameters
if grantType != "refresh_token" {
return &TokenError{
Error: UNSUPPORTED_GRANT_TYPE,
Error: UnsupportedGrantType,
ErrorDescription: "grant_type should be refresh_token",
}
}
application := GetApplicationByClientId(clientId)
if application == nil {
return &TokenError{
Error: INVALID_CLIENT,
Error: InvalidClient,
ErrorDescription: "client_id is invalid",
}
}
if clientSecret != "" && application.ClientSecret != clientSecret {
return &TokenError{
Error: INVALID_CLIENT,
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}
}
@ -399,7 +399,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
existed, err := adapter.Engine.Get(&token)
if err != nil || !existed {
return &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "refresh token is invalid, expired or revoked",
}
}
@ -408,7 +408,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
_, err = ParseJwtToken(refreshToken, cert)
if err != nil {
return &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}
}
@ -416,14 +416,14 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
user := getUser(application.Organization, token.User)
if user.IsForbidden {
return &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}
}
newAccessToken, newRefreshToken, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return &TokenError{
Error: ENDPOINT_ERROR,
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
}
@ -464,6 +464,7 @@ func pkceChallenge(verifier string) string {
return challenge
}
// IsGrantTypeValid
// Check if grantType is allowed in the current application
// authorization_code is allowed by default
func IsGrantTypeValid(method string, grantTypes []string) bool {
@ -478,11 +479,12 @@ func IsGrantTypeValid(method string, grantTypes []string) bool {
return false
}
// GetAuthorizationCodeToken
// Authorization code flow
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, *TokenError) {
if code == "" {
return nil, &TokenError{
Error: INVALID_REQUEST,
Error: InvalidRequest,
ErrorDescription: "authorization code should not be empty",
}
}
@ -490,21 +492,21 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
token := getTokenByCode(code)
if token == nil {
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "authorization code is invalid",
}
}
if token.CodeIsUsed {
// anti replay attacks
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "authorization code has been used",
}
}
if token.CodeChallenge != "" && pkceChallenge(verifier) != token.CodeChallenge {
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "verifier is invalid",
}
}
@ -514,13 +516,13 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
// but if it is provided, it must be accurate.
if token.CodeChallenge == "" {
return nil, &TokenError{
Error: INVALID_CLIENT,
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}
} else {
if clientSecret != "" {
return nil, &TokenError{
Error: INVALID_CLIENT,
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}
}
@ -529,7 +531,7 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
if application.Name != token.Application {
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "the token is for wrong application (client_id)",
}
}
@ -537,39 +539,40 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
if time.Now().Unix() > token.CodeExpireIn {
// code must be used within 5 minutes
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "authorization code has expired",
}
}
return token, nil
}
// GetPasswordToken
// Resource Owner Password Credentials flow
func GetPasswordToken(application *Application, username string, password string, scope string, host string) (*Token, *TokenError) {
user := getUser(application.Organization, username)
if user == nil {
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "the user does not exist",
}
}
msg := CheckPassword(user, password)
if msg != "" {
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "invalid username or password",
}
}
if user.IsForbidden {
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "the user is forbidden to sign in, please contact the administrator",
}
}
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
if err != nil {
return nil, &TokenError{
Error: ENDPOINT_ERROR,
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
}
@ -592,11 +595,12 @@ func GetPasswordToken(application *Application, username string, password string
return token, nil
}
// GetClientCredentialsToken
// Client Credentials flow
func GetClientCredentialsToken(application *Application, clientSecret string, scope string, host string) (*Token, *TokenError) {
if application.ClientSecret != clientSecret {
return nil, &TokenError{
Error: INVALID_CLIENT,
Error: InvalidClient,
ErrorDescription: "client_secret is invalid",
}
}
@ -608,7 +612,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
accessToken, _, err := generateJwtToken(application, nullUser, "", scope, host)
if err != nil {
return nil, &TokenError{
Error: ENDPOINT_ERROR,
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
}
@ -630,6 +634,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
return token, nil
}
// GetTokenByUser
// Implicit flow
func GetTokenByUser(application *Application, user *User, scope string, host string) (*Token, error) {
accessToken, refreshToken, err := generateJwtToken(application, user, "", scope, host)
@ -655,12 +660,13 @@ func GetTokenByUser(application *Application, user *User, scope string, host str
return token, nil
}
// GetWechatMiniProgramToken
// Wechat Mini Program flow
func GetWechatMiniProgramToken(application *Application, code string, host string, username string, avatar string) (*Token, *TokenError) {
mpProvider := GetWechatMiniProgramProvider(application)
if mpProvider == nil {
return nil, &TokenError{
Error: INVALID_CLIENT,
Error: InvalidClient,
ErrorDescription: "the application does not support wechat mini program",
}
}
@ -669,14 +675,14 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
session, err := mpIdp.GetSessionByCode(code)
if err != nil {
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("get wechat mini program session error: %s", err.Error()),
}
}
openId, unionId := session.Openid, session.Unionid
if openId == "" && unionId == "" {
return nil, &TokenError{
Error: INVALID_REQUEST,
Error: InvalidRequest,
ErrorDescription: "the wechat mini program session is invalid",
}
}
@ -684,11 +690,11 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
if user == nil {
if !application.EnableSignUp {
return nil, &TokenError{
Error: INVALID_GRANT,
Error: InvalidGrant,
ErrorDescription: "the application does not allow to sign up new account",
}
}
//Add new user
// Add new user
var name string
if username != "" {
name = username
@ -703,13 +709,16 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
Avatar: avatar,
SignupApplication: application.Name,
WeChat: openId,
WeChatUnionId: unionId,
Type: "normal-user",
CreatedTime: util.GetCurrentTime(),
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
Properties: map[string]string{
UserPropertiesWechatOpenId: openId,
UserPropertiesWechatUnionId: unionId,
},
}
AddUser(user)
}
@ -717,7 +726,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
accessToken, refreshToken, err := generateJwtToken(application, user, "", "", host)
if err != nil {
return nil, &TokenError{
Error: ENDPOINT_ERROR,
Error: EndpointError,
ErrorDescription: fmt.Sprintf("generate jwt token error: %s", err.Error()),
}
}
@ -729,7 +738,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
Application: application.Name,
Organization: user.Owner,
User: user.Name,
Code: session.SessionKey, //a trick, because miniprogram does not use the code, so use the code field to save the session_key
Code: session.SessionKey, // a trick, because miniprogram does not use the code, so use the code field to save the session_key
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: application.ExpireInHours * 60,

View File

@ -88,7 +88,7 @@ type CasAnyAttribute struct {
type CasAuthenticationSuccessWrapper struct {
AuthenticationSuccess *CasAuthenticationSuccess // the token we issued
Service string //to which service this token is issued
Service string // to which service this token is issued
UserId string
}
@ -116,10 +116,10 @@ type Saml11AssertionArtifact struct {
InnerXML string `xml:",innerxml"`
}
//st is short for service ticket
// st is short for service ticket
var stToServiceResponse sync.Map
//pgt is short for proxy granting ticket
// pgt is short for proxy granting ticket
var pgtToServiceResponse sync.Map
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service, userId string) string {
@ -136,6 +136,7 @@ func GenerateId() {
panic("unimplemented")
}
// GetCasTokenByPgt
/**
@ret1: whether a token is found
@ret2: token, nil if not found
@ -150,6 +151,7 @@ func GetCasTokenByPgt(pgt string) (bool, *CasAuthenticationSuccess, string, stri
return false, nil, "", ""
}
// GetCasTokenByTicket
/**
@ret1: whether a token is found
@ret2: token, nil if not found
@ -207,6 +209,7 @@ func GenerateCasToken(userId string, service string) (string, error) {
}
}
// GetValidationBySaml
/**
@ret1: saml response
@ret2: the service URL who requested to issue this token
@ -241,11 +244,11 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
samlResponse := NewSamlResponse11(user, request.RequestID, host)
cert := getCertByApplication(application)
block, _ := pem.Decode([]byte(cert.PublicKey))
publicKey := base64.StdEncoding.EncodeToString(block.Bytes)
block, _ := pem.Decode([]byte(cert.Certificate))
certificate := base64.StdEncoding.EncodeToString(block.Bytes)
randomKeyStore := &X509Key{
PrivateKey: cert.PrivateKey,
X509Certificate: publicKey,
X509Certificate: certificate,
}
ctx := dsig.NewDefaultSigningContext(randomKeyStore)
@ -262,12 +265,11 @@ func GetValidationBySaml(samlRequest string, host string) (string, string, error
return "", "", fmt.Errorf("err: %s", err.Error())
}
return xmlStr, service, nil
}
func (c *CasAuthenticationSuccess) DeepCopy() CasAuthenticationSuccess {
res := *c
//copy proxy
// copy proxy
if c.Proxies != nil {
tmp := c.Proxies.DeepCopy()
res.Proxies = &tmp
@ -307,7 +309,6 @@ func (c *CasAttributes) DeepCopy() CasAttributes {
res.ExtraAttributes[i] = &tmp
}
return res
}
func (c *CasUserAttributes) DeepCopy() CasUserAttributes {
@ -316,11 +317,11 @@ func (c *CasUserAttributes) DeepCopy() CasUserAttributes {
Attributes: make([]*CasNamedAttribute, len(c.Attributes)),
}
for i, a := range c.AnyAttributes {
var tmp = *a
tmp := *a
res.AnyAttributes[i] = &tmp
}
for i, a := range c.Attributes {
var tmp = *a
tmp := *a
res.Attributes[i] = &tmp
}
return res

View File

@ -129,13 +129,13 @@ func ParseJwtToken(token string, cert *Cert) (*Claims, error) {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
// RSA public key
publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.PublicKey))
// RSA certificate
certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
if err != nil {
return nil, err
}
return publicKey, nil
return certificate, nil
})
if t != nil {

View File

@ -23,10 +23,10 @@ import (
func TestGenerateRsaKeys(t *testing.T) {
fileId := "token_jwt_key"
publicKey, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
certificate, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
// Write certificate (aka public key) to file.
util.WriteStringToPath(publicKey, fmt.Sprintf("%s.pem", fileId))
// Write certificate (aka certificate) to file.
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))
// Write private key to file.
util.WriteStringToPath(privateKey, fmt.Sprintf("%s.key", fileId))

View File

@ -20,9 +20,15 @@ import (
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/webauthn"
"xorm.io/core"
)
const (
UserPropertiesWechatUnionId = "wechatUnionId"
UserPropertiesWechatOpenId = "wechatOpenId"
)
type User struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
@ -72,35 +78,39 @@ type User struct {
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Github string `xorm:"varchar(100)" json:"github"`
Google string `xorm:"varchar(100)" json:"google"`
QQ string `xorm:"qq varchar(100)" json:"qq"`
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
WeChatUnionId string `xorm:"varchar(100)" json:"unionId"`
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
Lark string `xorm:"lark varchar(100)" json:"lark"`
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
Apple string `xorm:"apple varchar(100)" json:"apple"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
Slack string `xorm:"slack varchar(100)" json:"slack"`
Steam string `xorm:"steam varchar(100)" json:"steam"`
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin vachar(100)" json:"douyin"`
Custom string `xorm:"custom varchar(100)" json:"custom"`
GitHub string `xorm:"github varchar(100)" json:"github"`
Google string `xorm:"varchar(100)" json:"google"`
QQ string `xorm:"qq varchar(100)" json:"qq"`
WeChat string `xorm:"wechat varchar(100)" json:"wechat"`
Facebook string `xorm:"facebook varchar(100)" json:"facebook"`
DingTalk string `xorm:"dingtalk varchar(100)" json:"dingtalk"`
Weibo string `xorm:"weibo varchar(100)" json:"weibo"`
Gitee string `xorm:"gitee varchar(100)" json:"gitee"`
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
Lark string `xorm:"lark varchar(100)" json:"lark"`
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
Alipay string `xorm:"alipay varchar(100)" json:"alipay"`
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
Apple string `xorm:"apple varchar(100)" json:"apple"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
Slack string `xorm:"slack varchar(100)" json:"slack"`
Steam string `xorm:"steam varchar(100)" json:"steam"`
Bilibili string `xorm:"bilibili varchar(100)" json:"bilibili"`
Okta string `xorm:"okta varchar(100)" json:"okta"`
Douyin string `xorm:"douyin varchar(100)" json:"douyin"`
Custom string `xorm:"custom varchar(100)" json:"custom"`
WebauthnCredentials []webauthn.Credential `xorm:"webauthnCredentials blob" json:"webauthnCredentials"`
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
Properties map[string]string `json:"properties"`
Roles []*Role `json:"roles"`
Permissions []*Permission `json:"permissions"`
}
type Userinfo struct {
@ -237,7 +247,7 @@ func getUserByWechatId(wechatOpenId string, wechatUnionId string) *User {
wechatUnionId = wechatOpenId
}
user := &User{}
existed, err := adapter.Engine.Where("wechat = ? OR wechat = ? OR unionid = ?", wechatOpenId, wechatUnionId, wechatUnionId).Get(user)
existed, err := adapter.Engine.Where("wechat = ? OR wechat = ?", wechatOpenId, wechatUnionId).Get(user)
if err != nil {
panic(err)
}
@ -267,6 +277,42 @@ func GetUserByEmail(owner string, email string) *User {
}
}
func GetUserByPhone(owner string, phone string) *User {
if owner == "" || phone == "" {
return nil
}
user := User{Owner: owner, Phone: phone}
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUserByUserId(owner string, userId string) *User {
if owner == "" || userId == "" {
return nil
}
user := User{Owner: owner, Id: userId}
existed, err := adapter.Engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUser(id string) *User {
owner, name := util.GetOwnerAndNameFromId(id)
return getUser(owner, name)
@ -326,9 +372,11 @@ func UpdateUser(id string, user *User, columns []string, isGlobalAdmin bool) boo
}
if len(columns) == 0 {
columns = []string{"owner", "display_name", "avatar",
columns = []string{
"owner", "display_name", "avatar",
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "signup_application",
"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", "webauthnCredentials",
}
}
if isGlobalAdmin {
columns = append(columns, "name", "email", "phone")
@ -373,6 +421,10 @@ func AddUser(user *User) bool {
}
organization := GetOrganizationByUser(user)
if organization == nil {
return false
}
user.UpdateUserPassword(organization)
user.UpdateUserHash()
@ -395,10 +447,10 @@ func AddUsers(users []*User) bool {
return false
}
//organization := GetOrganizationByUser(users[0])
// organization := GetOrganizationByUser(users[0])
for _, user := range users {
// this function is only used for syncer or batch upload, so no need to encrypt the password
//user.UpdateUserPassword(organization)
// user.UpdateUserPassword(organization)
user.UpdateUserHash()
user.PreHash = user.Hash

View File

@ -37,11 +37,11 @@ func TestSyncAvatarsFromGitHub(t *testing.T) {
users := GetGlobalUsers()
for _, user := range users {
if user.Github == "" {
if user.GitHub == "" {
continue
}
user.Avatar = fmt.Sprintf("https://avatars.githubusercontent.com/%s", user.Github)
user.Avatar = fmt.Sprintf("https://avatars.githubusercontent.com/%s", user.GitHub)
updateUserColumn("avatar", user)
}
}

View File

@ -106,6 +106,10 @@ func setUserProperty(user *User, field string, value string) {
if value == "" {
delete(user.Properties, field)
} else {
if user.Properties == nil {
user.Properties = make(map[string]string)
}
user.Properties[field] = value
}
}

103
object/user_webauthn.go Normal file
View File

@ -0,0 +1,103 @@
// Copyright 2022 The casbin 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/base64"
"net/url"
"strings"
"github.com/astaxie/beego"
"github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn"
)
func GetWebAuthnObject(host string) *webauthn.WebAuthn {
var err error
origin := beego.AppConfig.String("origin")
if origin == "" {
_, origin = getOriginFromHost(host)
}
localUrl, err := url.Parse(origin)
if err != nil {
panic("error when parsing origin:" + err.Error())
}
webAuthn, err := webauthn.New(&webauthn.Config{
RPDisplayName: beego.AppConfig.String("appname"), // Display Name for your site
RPID: strings.Split(localUrl.Host, ":")[0], // Generally the domain name for your site, it's ok because splits cannot return empty array
RPOrigin: origin, // The origin URL for WebAuthn requests
// RPIcon: "https://duo.com/logo.png", // Optional icon URL for your site
})
if err != nil {
panic(err)
}
return webAuthn
}
// WebAuthnID
// implementation of webauthn.User interface
func (user *User) WebAuthnID() []byte {
return []byte(user.GetId())
}
func (user *User) WebAuthnName() string {
return user.Name
}
func (user *User) WebAuthnDisplayName() string {
return user.DisplayName
}
func (user *User) WebAuthnCredentials() []webauthn.Credential {
return user.WebauthnCredentials
}
func (user *User) WebAuthnIcon() string {
return user.Avatar
}
// CredentialExcludeList returns a CredentialDescriptor array filled with all the user's credentials
func (user *User) CredentialExcludeList() []protocol.CredentialDescriptor {
credentials := user.WebAuthnCredentials()
credentialExcludeList := []protocol.CredentialDescriptor{}
for _, cred := range credentials {
descriptor := protocol.CredentialDescriptor{
Type: protocol.PublicKeyCredentialType,
CredentialID: cred.ID,
}
credentialExcludeList = append(credentialExcludeList, descriptor)
}
return credentialExcludeList
}
func (user *User) AddCredentials(credential webauthn.Credential, isGlobalAdmin bool) bool {
user.WebauthnCredentials = append(user.WebauthnCredentials, credential)
return UpdateUser(user.GetId(), user, []string{"webauthnCredentials"}, isGlobalAdmin)
}
func (user *User) DeleteCredentials(credentialIdBase64 string) bool {
for i, credential := range user.WebauthnCredentials {
if base64.StdEncoding.EncodeToString(credential.ID) == credentialIdBase64 {
user.WebauthnCredentials = append(user.WebauthnCredentials[0:i], user.WebauthnCredentials[i+1:]...)
return UpdateUserForAllFields(user.GetId(), user)
}
}
return false
}

Some files were not shown because too many files have changed in this diff Show More