Compare commits

...

44 Commits

Author SHA1 Message Date
Yang Luo
cba338eef2 Merge pull request #973 from qianxi0410/eslint
feat(web): add some eslint rules
2022-08-07 00:41:51 +08:00
qianxi0410
c428de6e42 feat: fix some comma dangle 2022-08-07 00:17:27 +08:00
qianxi0410
9bca6bb72e feat: no-multi-spacing 2022-08-07 00:06:20 +08:00
qianxi0410
cd966116d4 feat: comma dangle 2022-08-06 23:54:56 +08:00
qianxi0410
9abf1b9d73 feat: key spacing 2022-08-06 23:47:28 +08:00
qianxi0410
6aaba6debd feat: space between infix op 2022-08-06 23:43:09 +08:00
qianxi0410
77565712e0 feat: no-multi-empty-lines 2022-08-06 23:38:03 +08:00
qianxi0410
d025259db7 feat: indent 2022-08-06 23:36:20 +08:00
Artem
aafdc546fa fix: panic when creating a user in a non-existent org (#969) 2022-08-06 22:30:56 +08:00
q1anx1
539ca2d731 chore(web): add fix command (#964) 2022-08-05 23:40:04 +08:00
Ryao
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
Gucheng Wang
e94ada9ea2 Fix new accountItem. 2022-08-05 15:36:07 +08:00
Resulte Lee
4ea482223d feat: add geetest captcha (#953) 2022-08-04 20:55:04 +08:00
Gucheng Wang
d55ae7d1d2 Enable some other DBs 2022-08-04 20:28:09 +08:00
imp2002
d72e00605f fix: updateProviderField when add provider payment (#952) 2022-08-04 19:39:25 +08:00
zzjin
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
q1anx1
13404d6035 feat: fix binding after registration causes the page to crash (#945) 2022-08-01 21:08:10 +08:00
Mikey
afa9c530ad fix: panic triggered when user is nil (#940) 2022-07-31 23:23:36 +08:00
Yang Luo
1600615aca Support sqlite3 DB 2022-07-31 18:11:18 +08:00
Mikey
2bb8491499 fix: unable to get user if profile is private (#936) 2022-07-31 10:54:41 +08:00
Mikey
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
q1anx1
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
Yixiang Zhao
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
Mikey
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
Yang Luo
11695220a8 Use user.GetId() 2022-07-30 17:40:30 +08:00
Resulte Lee
155660b0d7 feat: get user api return roles and permissions (#929) 2022-07-30 17:31:56 +08:00
imp2002
1c72f5300c feat: fix 'Enable code sign' is not displayed in the login page (#925) 2022-07-28 23:11:33 +08:00
q1anx1
3dd56195d9 fix: fix the problem of link error (#923) 2022-07-28 21:52:10 +08:00
Resulte Lee
8865244262 fix: add oauth login auto close page (#915) 2022-07-26 23:03:55 +08:00
Yixiang Zhao
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
Resulte Lee
bdc5c92ef0 fix: send code missing parameter & show more detail responseError (#910) 2022-07-25 23:46:38 +08:00
SLingyu
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
SLingyu
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
leoshine
6f6159be07 feat: add GET method of logout API (#903) 2022-07-22 21:13:49 +08:00
Gucheng Wang
3e4dbc2dcb fix: URL bug in getUploadFileUrl function 2022-07-20 17:49:11 +08:00
Yixiang Zhao
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
q1anx1
1839252c30 chore(web): sort import members (#895) 2022-07-18 20:57:38 +08:00
q1anx1
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
Yang Luo
a0b0e186b7 Improve i18n code and data. 2022-07-17 17:56:43 +08:00
Yang Luo
8c7f235ee1 Fix bug in uploadFile()'s URL. 2022-07-17 14:29:06 +08:00
waltcow
a0a762aa6f fix: typo in field tag in BilibiliUserInfo (#890) 2022-07-17 11:31:43 +08:00
Yixiang Zhao
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
Yixiang Zhao
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
132 changed files with 1463 additions and 659 deletions

View File

@@ -78,6 +78,7 @@ 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, *, *
@@ -92,6 +93,7 @@ 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, *, *

82
captcha/geetest.go Normal file
View File

@@ -0,0 +1,82 @@
// 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/ioutil"
"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 := ioutil.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

@@ -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

@@ -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)

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

@@ -80,12 +80,16 @@ 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")
email := c.Input().Get("email")
phone := c.Input().Get("phone")
userId := c.Input().Get("userId")
owner := c.Input().Get("owner")
@@ -96,7 +100,7 @@ func (c *ApiController) GetUser() {
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
@@ -104,14 +108,24 @@ func (c *ApiController) GetUser() {
}
var user *object.User
if email != "" {
switch {
case email != "":
user = object.GetUserByEmail(owner, email)
} else if userId != "" {
case phone != "":
user = object.GetUserByPhone(owner, phone)
case userId != "":
user = object.GetUserByUserId(owner, userId)
} else {
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)
c.ServeJSON()
}
@@ -252,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

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
}
@@ -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)

2
go.mod
View File

@@ -14,6 +14,7 @@ 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
@@ -23,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

2
go.sum
View File

@@ -125,6 +125,7 @@ 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=
@@ -173,6 +174,7 @@ github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptG
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=

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

@@ -144,7 +144,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 {

View File

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

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"
)

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

@@ -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 {
@@ -236,7 +240,7 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
allowed := true
var err error
for _, permission := range permissions {
if !permission.IsEnabled {
if !permission.IsEnabled || len(permission.Users) == 0 {
continue
}

View File

@@ -25,6 +25,7 @@ import (
func InitDb() {
existed := initBuiltInOrganization()
if !existed {
initBuiltInPermission()
initBuiltInProvider()
initBuiltInUser()
initBuiltInApplication()
@@ -70,6 +71,8 @@ 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"},
@@ -167,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
@@ -183,7 +186,7 @@ func initBuiltInCert() {
CryptoAlgorithm: "RS256",
BitSize: 4096,
ExpireInYears: 20,
PublicKey: tokenJwtPublicKey,
Certificate: tokenJwtCertificate,
PrivateKey: tokenJwtPrivateKey,
}
AddCert(cert)
@@ -230,3 +233,25 @@ func initBuiltInProvider() {
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

@@ -97,7 +97,7 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
//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

@@ -229,3 +229,13 @@ func removePolicies(permission *Permission) {
panic(err)
}
}
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

@@ -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

@@ -214,7 +214,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

@@ -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

@@ -36,7 +36,7 @@ import (
)
//returns a saml2 response
func NewSamlResponse(user *User, host string, publicKey string, destination string, iss string, requestId string, redirectUri []string) (*etree.Element, error) {
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",
@@ -177,8 +177,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 +199,7 @@ func GetSamlMeta(application *Application, host string) (*IdpEntityDescriptor, e
KeyInfo: KeyInfo{
X509Data: X509Data{
X509Certificate: X509Certificate{
Cert: publicKey,
Cert: certificate,
},
},
},
@@ -248,18 +248,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

View File

@@ -72,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

@@ -241,11 +241,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)

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

@@ -73,7 +73,7 @@ type User struct {
LastSigninTime string `xorm:"varchar(100)" json:"lastSigninTime"`
LastSigninIp string `xorm:"varchar(100)" json:"lastSigninIp"`
Github string `xorm:"varchar(100)" json:"github"`
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"`
@@ -104,6 +104,9 @@ type User struct {
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 {
@@ -270,6 +273,24 @@ 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
@@ -396,6 +417,10 @@ func AddUser(user *User) bool {
}
organization := GetOrganizationByUser(user)
if organization == nil {
return false
}
user.UpdateUserPassword(organization)
user.UpdateUserHash()

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
}
}

View File

@@ -28,7 +28,7 @@ type AlipayPaymentProvider struct {
Client *alipay.Client
}
func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider {
func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) *AlipayPaymentProvider {
pp := &AlipayPaymentProvider{}
client, err := alipay.NewClient(appId, appPrivateKey, true)
@@ -36,7 +36,7 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
panic(err)
}
err = client.SetCertSnByContent([]byte(appPublicKey), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
err = client.SetCertSnByContent([]byte(appCertificate), []byte(authorityRootPublicKey), []byte(authorityPublicKey))
if err != nil {
panic(err)
}

View File

@@ -22,9 +22,9 @@ type PaymentProvider interface {
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
}
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appCertificate string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
if typ == "Alipay" {
return NewAlipayPaymentProvider(appId, appPublicKey, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
return NewAlipayPaymentProvider(appId, appCertificate, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
} else if typ == "GC" {
return NewGcPaymentProvider(appId, clientSecret, host)
}

View File

@@ -122,6 +122,10 @@ func AuthzFilter(ctx *context.Context) {
urlPath := getUrlPath(ctx.Request.URL.Path)
objOwner, objName := getObject(ctx)
if strings.HasPrefix(urlPath, "/api/notify-payment") {
urlPath = "/api/notify-payment"
}
isAllowed := authz.IsAllowed(subOwner, subName, method, urlPath, objOwner, objName)
result := "deny"

View File

@@ -48,7 +48,7 @@ func initAPI() {
beego.Router("/api/signup", &controllers.ApiController{}, "POST:Signup")
beego.Router("/api/login", &controllers.ApiController{}, "POST:Login")
beego.Router("/api/get-app-login", &controllers.ApiController{}, "GET:GetApplicationLogin")
beego.Router("/api/logout", &controllers.ApiController{}, "POST:Logout")
beego.Router("/api/logout", &controllers.ApiController{}, "GET,POST:Logout")
beego.Router("/api/get-account", &controllers.ApiController{}, "GET:GetAccount")
beego.Router("/api/userinfo", &controllers.ApiController{}, "GET:GetUserinfo")
beego.Router("/api/unlink", &controllers.ApiController{}, "POST:Unlink")

View File

@@ -2418,6 +2418,21 @@
}
},
"/api/logout": {
"get": {
"tags": [
"Login API"
],
"description": "logout the current user",
"operationId": "ApiController.Logout",
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
},
"post": {
"tags": [
"Login API"
@@ -3096,14 +3111,120 @@
],
"operationId": "ApiController.VerifyCaptcha"
}
},
"/api/webauthn/signin/begin": {
"get": {
"tags": [
"Login API"
],
"description": "WebAuthn Login Flow 1st stage",
"operationId": "ApiController.WebAuthnSigninBegin",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "owner",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "name",
"description": "name",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The CredentialAssertion object",
"schema": {
"$ref": "#/definitions/protocol.CredentialAssertion"
}
}
}
}
},
"/api/webauthn/signin/finish": {
"post": {
"tags": [
"Login API"
],
"description": "WebAuthn Login Flow 2nd stage",
"operationId": "ApiController.WebAuthnSigninBegin",
"parameters": [
{
"in": "body",
"name": "body",
"description": "authenticator assertion Response",
"required": true,
"schema": {
"$ref": "#/definitions/protocol.CredentialAssertionResponse"
}
}
],
"responses": {
"200": {
"description": "\"The Response object\"",
"schema": {
"$ref": "#/definitions/Response"
}
}
}
}
},
"/api/webauthn/signup/begin": {
"get": {
"tags": [
"User API"
],
"description": "WebAuthn Registration Flow 1st stage",
"operationId": "ApiController.WebAuthnSignupBegin",
"responses": {
"200": {
"description": "The CredentialCreationOptions object",
"schema": {
"$ref": "#/definitions/protocol.CredentialCreation"
}
}
}
}
},
"/api/webauthn/signup/finish": {
"post": {
"tags": [
"User API"
],
"description": "WebAuthn Registration Flow 2nd stage",
"operationId": "ApiController.WebAuthnSignupFinish",
"parameters": [
{
"in": "body",
"name": "body",
"description": "authenticator attestation Response",
"required": true,
"schema": {
"$ref": "#/definitions/protocol.CredentialCreationResponse"
}
}
],
"responses": {
"200": {
"description": "\"The Response object\"",
"schema": {
"$ref": "#/definitions/Response"
}
}
}
}
}
},
"definitions": {
"2127.0xc000398090.false": {
"2127.0xc000427560.false": {
"title": "false",
"type": "object"
},
"2161.0xc0003980c0.false": {
"2161.0xc000427590.false": {
"title": "false",
"type": "object"
},
@@ -3221,10 +3342,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/2127.0xc000398090.false"
"$ref": "#/definitions/2127.0xc000427560.false"
},
"data2": {
"$ref": "#/definitions/2161.0xc0003980c0.false"
"$ref": "#/definitions/2161.0xc000427590.false"
},
"msg": {
"type": "string"
@@ -3329,12 +3450,18 @@
"enablePassword": {
"type": "boolean"
},
"enableSamlCompress": {
"type": "boolean"
},
"enableSignUp": {
"type": "boolean"
},
"enableSigninSession": {
"type": "boolean"
},
"enableWebAuthn": {
"type": "boolean"
},
"expireInHours": {
"type": "integer",
"format": "int64"
@@ -3444,7 +3571,7 @@
"privateKey": {
"type": "string"
},
"publicKey": {
"certificate": {
"type": "string"
},
"scope": {
@@ -4507,6 +4634,12 @@
"updatedTime": {
"type": "string"
},
"webauthnCredentials": {
"type": "array",
"items": {
"$ref": "#/definitions/webauthn.Credential"
}
},
"wechat": {
"type": "string"
},
@@ -4596,6 +4729,26 @@
}
}
},
"protocol.CredentialAssertion": {
"title": "CredentialAssertion",
"type": "object"
},
"protocol.CredentialAssertionResponse": {
"title": "CredentialAssertionResponse",
"type": "object"
},
"protocol.CredentialCreation": {
"title": "CredentialCreation",
"type": "object"
},
"protocol.CredentialCreationResponse": {
"title": "CredentialCreationResponse",
"type": "object"
},
"webauthn.Credential": {
"title": "Credential",
"type": "object"
},
"xorm.Engine": {
"title": "Engine",
"type": "object"

View File

@@ -1584,6 +1584,16 @@ paths:
schema:
$ref: '#/definitions/object.TokenError'
/api/logout:
get:
tags:
- Login API
description: logout the current user
operationId: ApiController.Logout
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
post:
tags:
- Login API
@@ -2028,11 +2038,80 @@ paths:
tags:
- Verification API
operationId: ApiController.VerifyCaptcha
/api/webauthn/signin/begin:
get:
tags:
- Login API
description: WebAuthn Login Flow 1st stage
operationId: ApiController.WebAuthnSigninBegin
parameters:
- in: query
name: owner
description: owner
required: true
type: string
- in: query
name: name
description: name
required: true
type: string
responses:
"200":
description: The CredentialAssertion object
schema:
$ref: '#/definitions/protocol.CredentialAssertion'
/api/webauthn/signin/finish:
post:
tags:
- Login API
description: WebAuthn Login Flow 2nd stage
operationId: ApiController.WebAuthnSigninBegin
parameters:
- in: body
name: body
description: authenticator assertion Response
required: true
schema:
$ref: '#/definitions/protocol.CredentialAssertionResponse'
responses:
"200":
description: '"The Response object"'
schema:
$ref: '#/definitions/Response'
/api/webauthn/signup/begin:
get:
tags:
- User API
description: WebAuthn Registration Flow 1st stage
operationId: ApiController.WebAuthnSignupBegin
responses:
"200":
description: The CredentialCreationOptions object
schema:
$ref: '#/definitions/protocol.CredentialCreation'
/api/webauthn/signup/finish:
post:
tags:
- User API
description: WebAuthn Registration Flow 2nd stage
operationId: ApiController.WebAuthnSignupFinish
parameters:
- in: body
name: body
description: authenticator attestation Response
required: true
schema:
$ref: '#/definitions/protocol.CredentialCreationResponse'
responses:
"200":
description: '"The Response object"'
schema:
$ref: '#/definitions/Response'
definitions:
2127.0xc000398090.false:
2127.0xc000427560.false:
title: "false"
type: object
2161.0xc0003980c0.false:
2161.0xc000427590.false:
title: "false"
type: object
Response:
@@ -2113,9 +2192,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/2127.0xc000398090.false'
$ref: '#/definitions/2127.0xc000427560.false'
data2:
$ref: '#/definitions/2161.0xc0003980c0.false'
$ref: '#/definitions/2161.0xc000427590.false'
msg:
type: string
name:
@@ -2185,10 +2264,14 @@ definitions:
type: boolean
enablePassword:
type: boolean
enableSamlCompress:
type: boolean
enableSignUp:
type: boolean
enableSigninSession:
type: boolean
enableWebAuthn:
type: boolean
expireInHours:
type: integer
format: int64
@@ -2263,7 +2346,7 @@ definitions:
type: string
privateKey:
type: string
publicKey:
certificate:
type: string
scope:
type: string
@@ -2977,6 +3060,10 @@ definitions:
type: string
updatedTime:
type: string
webauthnCredentials:
type: array
items:
$ref: '#/definitions/webauthn.Credential'
wechat:
type: string
wecom:
@@ -3035,6 +3122,21 @@ definitions:
type: string
url:
type: string
protocol.CredentialAssertion:
title: CredentialAssertion
type: object
protocol.CredentialAssertionResponse:
title: CredentialAssertionResponse
type: object
protocol.CredentialCreation:
title: CredentialCreation
type: object
protocol.CredentialCreationResponse:
title: CredentialCreationResponse
type: object
webauthn.Credential:
title: Credential
type: object
xorm.Engine:
title: Engine
type: object

View File

@@ -17,7 +17,9 @@ package util
import (
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
)
func GetHmacSha1(keyStr, value string) string {
@@ -28,3 +30,10 @@ func GetHmacSha1(keyStr, value string) string {
return res
}
func GetHmacSha256(key string, data string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(data))
return hex.EncodeToString(mac.Sum(nil))
}

View File

@@ -52,8 +52,10 @@ func ParseFloat(s string) float64 {
}
func ParseBool(s string) bool {
if s == "\x01" {
if s == "\x01" || s == "true" {
return true
} else if s == "false" {
return false
}
i := ParseInt(s)

1
web/.env Normal file
View File

@@ -0,0 +1 @@
PUBLIC_URL=.

View File

@@ -21,7 +21,7 @@
"rules": {
// "eqeqeq": "error",
"semi": ["error", "always"],
// "indent": ["error", 2],
"indent": ["error", 2],
// follow antd's style guide
"quotes": ["error", "double"],
"jsx-quotes": ["error", "prefer-double"],
@@ -45,6 +45,23 @@
"curly": ["error", "all"],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"no-mixed-spaces-and-tabs": "error",
"sort-imports": ["error", {
"ignoreDeclarationSort": true
}],
"no-multiple-empty-lines": ["error", { "max": 1, "maxBOF": 0, "maxEOF": 0 }],
"space-unary-ops": ["error", { "words": true, "nonwords": false }],
"space-infix-ops": "error",
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
"comma-style": ["error", "last"],
"comma-dangle": ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "never",
"exports": "never",
"functions": "never"
}],
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
"react/prop-types": "off",
"react/display-name": "off",

View File

@@ -34,7 +34,7 @@ module.exports = {
"/cas/validate": {
target: "http://localhost:8000",
changeOrigin: true,
}
},
},
},
plugins: [

View File

@@ -39,7 +39,8 @@
"test": "craco test",
"eject": "craco eject",
"crowdin:sync": "crowdin upload && crowdin download",
"preinstall": "node -e \"if (process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('Use yarn for installing: https://yarnpkg.com/en/docs/install')\""
"preinstall": "node -e \"if (process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('Use yarn for installing: https://yarnpkg.com/en/docs/install')\"",
"fix": "eslint --fix ."
},
"eslintConfig": {
"extends": "react-app"

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons";
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting";
import i18next from "i18next";
@@ -38,7 +38,7 @@ class AccountTable extends React.Component {
}
addRow(table) {
let row = {name: Setting.getNewRowNameForTable(table, "Please select an account item"), visible: true};
let row = {name: Setting.getNewRowNameForTable(table, "Please select an account item"), visible: true, viewRule: "Public", modifyRule: "Self"};
if (table === undefined) {
table = [];
}
@@ -86,6 +86,8 @@ class AccountTable extends React.Component {
{name: "Bio", displayName: i18next.t("user:Bio")},
{name: "Tag", displayName: i18next.t("user:Tag")},
{name: "Signup application", displayName: i18next.t("general:Signup application")},
{name: "Roles", displayName: i18next.t("general:Roles")},
{name: "Permissions", displayName: i18next.t("general:Permissions")},
{name: "3rd-party logins", displayName: i18next.t("user:3rd-party logins")},
{name: "Properties", displayName: i18next.t("user:Properties")},
{name: "Is admin", displayName: i18next.t("user:Is admin")},
@@ -114,7 +116,7 @@ class AccountTable extends React.Component {
}
</Select>
);
}
},
},
{
title: i18next.t("provider:visible"),
@@ -127,7 +129,7 @@ class AccountTable extends React.Component {
this.updateField(table, index, "visible", checked);
}} />
);
}
},
},
{
title: i18next.t("organization:viewRule"),
@@ -154,7 +156,7 @@ class AccountTable extends React.Component {
}
</Select>
);
}
},
},
{
title: i18next.t("organization:modifyRule"),
@@ -189,7 +191,7 @@ class AccountTable extends React.Component {
}
</Select>
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -209,7 +211,7 @@ class AccountTable extends React.Component {
</Tooltip>
</div>
);
}
},
},
];

View File

@@ -17,7 +17,7 @@ import "./App.less";
import {Helmet} from "react-helmet";
import * as Setting from "./Setting";
import {DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
import {Avatar, BackTop, Dropdown, Layout, Menu, Card, Result, Button} from "antd";
import {Avatar, BackTop, Button, Card, Dropdown, Layout, Menu, Result} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage";
@@ -235,15 +235,19 @@ class App extends Component {
AuthBackend.logout()
.then((res) => {
if (res.status === "ok") {
const owner = this.state.account.owner;
this.setState({
account: null
account: null,
});
Setting.showMessage("success", "Logged out successfully");
let redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
}else{
} else if (owner !== "built-in") {
Setting.goToLink(`${window.location.origin}/login/${owner}`);
} else {
Setting.goToLinkSoft(this, "/");
}
} else {
@@ -254,7 +258,7 @@ class App extends Component {
onUpdateAccount(account) {
this.setState({
account: account
account: account,
});
}
@@ -562,7 +566,7 @@ class App extends Component {
renderContent() {
if (!Setting.isMobile()) {
return (
<div style={{display: "flex", flex: "auto", width:"100%", flexDirection: "column"}}>
<div style={{display: "flex", flex: "auto", width: "100%", flexDirection: "column"}}>
<Layout style={{display: "flex", alignItems: "stretch"}}>
<Header style={{padding: "0", marginBottom: "3px"}}>
{
@@ -669,7 +673,9 @@ class App extends Component {
<Route exact path="/signup" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />)} />
<Route exact path="/login" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/:owner" render={(props) => this.renderHomeIfLoggedIn(<SelfLoginPage account={this.state.account} {...props} />)} />
<Route exact path="/auto-signup/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signup"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/signup/oauth/authorize" render={(props) => <SignupPage account={this.state.account} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/oauth/authorize" render={(props) => <LoginPage account={this.state.account} type={"code"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/login/saml/authorize/:owner/:applicationName" render={(props) => <LoginPage account={this.state.account} type={"saml"} mode={"signin"} {...props} onUpdateAccount={(account) => {this.onUpdateAccount(account);}} />} />
<Route exact path="/cas/:owner/:casApplicationName/logout" render={(props) => this.renderHomeIfLoggedIn(<CasLogout clearAccount={() => this.setState({account: null})} {...props} />)} />

View File

@@ -156,7 +156,7 @@ class ApplicationEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitApplicationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteApplication()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
@@ -181,7 +181,7 @@ class ApplicationEditPage extends React.Component {
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Logo"), i18next.t("general:Logo - Tooltip"))} :
</Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} :{}}>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} : {}}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :
@@ -583,7 +583,7 @@ class ApplicationEditPage extends React.Component {
{i18next.t("application:Copy signup page URL")}
</Button>
<br />
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
{
this.state.application.enablePassword ? (
<SignupPage application={this.state.application} />
@@ -603,7 +603,7 @@ class ApplicationEditPage extends React.Component {
{i18next.t("application:Copy signin page URL")}
</Button>
<br />
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems:"center", overflow:"auto", flexDirection:"column", flex: "auto"}}>
<div style={{position: "relative", width: "90%", border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", alignItems: "center", overflow: "auto", flexDirection: "column", flex: "auto"}}>
<LoginPage type={"login"} mode={"signin"} application={this.state.application} />
<div style={maskStyle}></div>
</div>

View File

@@ -99,7 +99,7 @@ class ApplicationListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -109,7 +109,7 @@ class ApplicationListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Display name"),
@@ -130,7 +130,7 @@ class ApplicationListPage extends BaseListPage {
<img src={text} alt={text} width={150} />
</a>
);
}
},
},
{
title: i18next.t("general:Organization"),
@@ -145,7 +145,7 @@ class ApplicationListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Providers"),
@@ -222,7 +222,7 @@ class ApplicationListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -30,6 +30,7 @@ class BaseListPage extends React.Component {
loading: false,
searchText: "",
searchedColumn: "",
isAuthorized: true,
};
}

View File

@@ -73,7 +73,7 @@ class CertEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitCertEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteCert()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
@@ -164,25 +164,25 @@ class CertEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("cert:Public key"), i18next.t("cert:Public key - Tooltip"))} :
{Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
</Col>
<Col span={9} >
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
copy(this.state.cert.publicKey);
Setting.showMessage("success", i18next.t("cert:Public key copied to clipboard successfully"));
copy(this.state.cert.certificate);
Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
}}
>
{i18next.t("cert:Copy public key")}
{i18next.t("cert:Copy certificate")}
</Button>
<Button type="primary" onClick={() => {
const blob = new Blob([this.state.cert.publicKey], {type: "text/plain;charset=utf-8"});
const blob = new Blob([this.state.cert.certificate], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "token_jwt_key.pem");
}}
>
{i18next.t("cert:Download public key")}
{i18next.t("cert:Download certificate")}
</Button>
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.publicKey} onChange={e => {
this.updateCertField("publicKey", e.target.value);
<TextArea autoSize={{minRows: 30, maxRows: 30}} value={this.state.cert.certificate} onChange={e => {
this.updateCertField("certificate", e.target.value);
}} />
</Col>
<Col span={1} />

View File

@@ -34,7 +34,7 @@ class CertListPage extends BaseListPage {
cryptoAlgorithm: "RS256",
bitSize: 4096,
expireInYears: 20,
publicKey: "",
certificate: "",
privateKey: "",
};
}
@@ -82,7 +82,7 @@ class CertListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -92,7 +92,7 @@ class CertListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Display name"),
@@ -169,7 +169,7 @@ class CertListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -16,7 +16,7 @@ import React, {useState} from "react";
import Cropper from "react-cropper";
import "cropperjs/dist/cropper.css";
import * as Setting from "./Setting";
import {Button, Row, Col, Modal} from "antd";
import {Button, Col, Modal, Row} from "antd";
import i18next from "i18next";
import * as ResourceBackend from "./backend/ResourceBackend";

View File

@@ -42,7 +42,7 @@ class LdapEditPage extends React.Component {
.then((res) => {
if (res.status === "ok") {
this.setState({
ldap: res.data
ldap: res.data,
});
} else {
Setting.showMessage("error", res.msg);
@@ -71,7 +71,7 @@ class LdapEditPage extends React.Component {
return (
<span style={{
color: "#faad14",
marginLeft: "20px"
marginLeft: "20px",
}}>{i18next.t("ldap:The Auto Sync option will sync all users to specify organization")}</span>
);
}

View File

@@ -23,7 +23,7 @@ class LdapListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
ldaps: null
ldaps: null,
};
}
@@ -65,7 +65,7 @@ class LdapListPage extends React.Component {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Organization"),
@@ -79,7 +79,7 @@ class LdapListPage extends React.Component {
{text}
</Link>
);
}
},
},
{
title: i18next.t("ldap:Server"),
@@ -89,7 +89,7 @@ class LdapListPage extends React.Component {
sorter: (a, b) => a.host.localeCompare(b.host),
render: (text, record, index) => {
return `${text}:${record.port}`;
}
},
},
{
title: i18next.t("ldap:Base DN"),
@@ -114,7 +114,7 @@ class LdapListPage extends React.Component {
render: (text, record, index) => {
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
<span style={{color: "#52c41a"}}>{text + " mins"}</span>);
}
},
},
{
title: i18next.t("ldap:Last Sync"),
@@ -124,7 +124,7 @@ class LdapListPage extends React.Component {
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
render: (text, record, index) => {
return text;
}
},
},
{
title: i18next.t("general:Action"),
@@ -148,7 +148,7 @@ class LdapListPage extends React.Component {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Col, Row, Table, Popconfirm} from "antd";
import {Button, Col, Popconfirm, Row, Table} from "antd";
import * as Setting from "./Setting";
import * as LdapBackend from "./backend/LdapBackend";
import i18next from "i18next";
@@ -87,7 +87,6 @@ class LdapSyncPage extends React.Component {
});
}
getLdapUser(ldap) {
LdapBackend.getLdapUser(ldap)
.then((res) => {

View File

@@ -48,7 +48,7 @@ class LdapTable extends React.Component {
passwd: "123",
baseDn: "ou=People,dc=example,dc=com",
autosync: 0,
lastSync: ""
lastSync: "",
};
}
@@ -104,7 +104,7 @@ class LdapTable extends React.Component {
{text}
</Link>
);
}
},
},
{
title: i18next.t("ldap:Server"),
@@ -114,7 +114,7 @@ class LdapTable extends React.Component {
sorter: (a, b) => a.host.localeCompare(b.host),
render: (text, record, index) => {
return `${text}:${record.port}`;
}
},
},
{
title: i18next.t("ldap:Base DN"),
@@ -132,7 +132,7 @@ class LdapTable extends React.Component {
render: (text, record, index) => {
return text === 0 ? (<span style={{color: "#faad14"}}>Disable</span>) : (
<span style={{color: "#52c41a"}}>{text + " mins"}</span>);
}
},
},
{
title: i18next.t("ldap:Last Sync"),
@@ -142,7 +142,7 @@ class LdapTable extends React.Component {
sorter: (a, b) => a.lastSync.localeCompare(b.lastSync),
render: (text, record, index) => {
return text;
}
},
},
{
title: i18next.t("general:Action"),
@@ -166,7 +166,7 @@ class LdapTable extends React.Component {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -97,7 +97,7 @@ class ModelEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitModelEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteModel()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -76,7 +76,7 @@ class ModelListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Name"),
@@ -92,7 +92,7 @@ class ModelListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -102,7 +102,7 @@ class ModelListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Display name"),
@@ -122,7 +122,7 @@ class ModelListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -143,7 +143,7 @@ class ModelListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -60,7 +60,7 @@ class OrganizationEditPage extends React.Component {
}
}
this.setState({
ldaps: resdata
ldaps: resdata,
});
});
}
@@ -91,7 +91,7 @@ class OrganizationEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitOrganizationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteOrganization()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :

View File

@@ -14,7 +14,7 @@
import React from "react";
import {Link} from "react-router-dom";
import {Button, Popconfirm, Switch, Table} from "antd";
import {Button, Popconfirm, Result, Switch, Table} from "antd";
import moment from "moment";
import * as Setting from "./Setting";
import * as OrganizationBackend from "./backend/OrganizationBackend";
@@ -57,6 +57,8 @@ class OrganizationListPage extends BaseListPage {
{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"},
@@ -110,7 +112,7 @@ class OrganizationListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -120,7 +122,7 @@ class OrganizationListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Display name"),
@@ -141,7 +143,7 @@ class OrganizationListPage extends BaseListPage {
<img src={text} alt={text} width={40} />
</a>
);
}
},
},
{
title: i18next.t("organization:Website URL"),
@@ -156,7 +158,7 @@ class OrganizationListPage extends BaseListPage {
{text}
</a>
);
}
},
},
{
title: i18next.t("general:Password type"),
@@ -190,7 +192,7 @@ class OrganizationListPage extends BaseListPage {
<img src={text} alt={text} width={40} />
</a>
);
}
},
},
{
title: i18next.t("organization:Soft deletion"),
@@ -202,7 +204,7 @@ class OrganizationListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -224,7 +226,7 @@ class OrganizationListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];
@@ -235,6 +237,17 @@ class OrganizationListPage extends BaseListPage {
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
};
if (!this.state.isAuthorized) {
return (
<Result
status="403"
title="403 Unauthorized"
subTitle={i18next.t("general:Sorry, you do not have permission to access this page.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>}
/>
);
}
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={organizations} rowKey="name" size="middle" bordered pagination={paginationProps}
@@ -272,6 +285,13 @@ class OrganizationListPage extends BaseListPage {
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (res.msg.includes("Unauthorized")) {
this.setState({
loading: false,
isAuthorized: false,
});
}
}
});
};

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Button, Col, Modal, Row, Input} from "antd";
import {Button, Col, Input, Modal, Row} from "antd";
import i18next from "i18next";
import React from "react";
import * as UserBackend from "./backend/UserBackend";

View File

@@ -152,7 +152,7 @@ class PaymentEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPaymentEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePayment()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -89,7 +89,7 @@ class PaymentListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:User"),
@@ -104,7 +104,7 @@ class PaymentListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Name"),
@@ -120,7 +120,7 @@ class PaymentListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -130,7 +130,7 @@ class PaymentListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
// {
// title: i18next.t("general:Display name"),
@@ -154,7 +154,7 @@ class PaymentListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("payment:Type"),
@@ -163,12 +163,12 @@ class PaymentListPage extends BaseListPage {
width: "140px",
align: "center",
filterMultiple: false,
filters: Setting.getProviderTypeOptions("Payment").map((o) => {return {text:o.id, value:o.name};}),
filters: Setting.getProviderTypeOptions("Payment").map((o) => {return {text: o.id, value: o.name};}),
sorter: true,
render: (text, record, index) => {
record.category = "Payment";
return Provider.getProviderLogoWidget(record);
}
},
},
{
title: i18next.t("payment:Product"),
@@ -221,7 +221,7 @@ class PaymentListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -67,7 +67,7 @@ class PaymentResultPage extends React.Component {
Setting.goToLink(payment.returnUrl);
}}>
{i18next.t("payment:Return to Website")}
</Button>
</Button>,
]}
/>
</div>
@@ -103,7 +103,7 @@ class PaymentResultPage extends React.Component {
Setting.goToLink(payment.returnUrl);
}}>
{i18next.t("payment:Return to Website")}
</Button>
</Button>,
]}
/>
</div>

View File

@@ -132,7 +132,7 @@ class PermissionEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitPermissionEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deletePermission()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :
@@ -143,6 +143,8 @@ class PermissionEditPage extends React.Component {
this.getUsers(owner);
this.getRoles(owner);
this.getModels(owner);
this.getResources(owner);
})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)

View File

@@ -33,7 +33,7 @@ class PermissionListPage extends BaseListPage {
roles: [],
resourceType: "Application",
resources: ["app-built-in"],
action: "Read",
actions: ["Read"],
effect: "Allow",
isEnabled: true,
};
@@ -81,7 +81,7 @@ class PermissionListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Name"),
@@ -97,7 +97,7 @@ class PermissionListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -107,7 +107,7 @@ class PermissionListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Display name"),
@@ -126,7 +126,7 @@ class PermissionListPage extends BaseListPage {
...this.getColumnSearchProps("users"),
render: (text, record, index) => {
return Setting.getTags(text);
}
},
},
{
title: i18next.t("role:Sub roles"),
@@ -137,7 +137,7 @@ class PermissionListPage extends BaseListPage {
...this.getColumnSearchProps("roles"),
render: (text, record, index) => {
return Setting.getTags(text);
}
},
},
{
title: i18next.t("permission:Resource type"),
@@ -159,7 +159,7 @@ class PermissionListPage extends BaseListPage {
...this.getColumnSearchProps("resources"),
render: (text, record, index) => {
return Setting.getTags(text);
}
},
},
{
title: i18next.t("permission:Actions"),
@@ -170,7 +170,7 @@ class PermissionListPage extends BaseListPage {
...this.getColumnSearchProps("actions"),
render: (text, record, index) => {
return Setting.getTags(text);
}
},
},
{
title: i18next.t("permission:Effect"),
@@ -194,7 +194,7 @@ class PermissionListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -214,7 +214,7 @@ class PermissionListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -85,7 +85,7 @@ class ProductEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProductEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProduct()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
@@ -110,7 +110,7 @@ class ProductEditPage extends React.Component {
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Image"), i18next.t("product:Image - Tooltip"))} :
</Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} :{}}>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} : {}}>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{Setting.getLabel(i18next.t("general:URL"), i18next.t("general:URL - Tooltip"))} :

View File

@@ -84,7 +84,7 @@ class ProductListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -94,7 +94,7 @@ class ProductListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Display name"),
@@ -115,7 +115,7 @@ class ProductListPage extends BaseListPage {
<img src={text} alt={text} width={150} />
</a>
);
}
},
},
{
title: i18next.t("product:Tag"),
@@ -240,7 +240,7 @@ class ProductListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -167,7 +167,7 @@ class ProviderEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitProviderEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteProvider()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
@@ -208,6 +208,8 @@ class ProviderEditPage extends React.Component {
this.updateProviderField("domain", Setting.getFullServerUrl());
} else if (value === "SAML") {
this.updateProviderField("type", "Aliyun IDaaS");
} else if (value === "Payment") {
this.updateProviderField("type", "Alipay");
} else if (value === "Captcha") {
this.updateProviderField("type", "Default");
}
@@ -267,7 +269,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
{
this.state.provider.type !== "WeCom" ? null : (
this.state.provider.type !== "WeCom" ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
@@ -416,7 +418,7 @@ class ProviderEditPage extends React.Component {
)
}
{
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" && this.state.provider.type !== "Okta" ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
@@ -622,7 +624,7 @@ class ProviderEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP public key"))} :
{Setting.getLabel(i18next.t("provider:IdP"), i18next.t("provider:IdP certificate"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.idP} onChange={e => {

View File

@@ -85,7 +85,7 @@ class ProviderListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -95,7 +95,7 @@ class ProviderListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Display name"),
@@ -128,17 +128,17 @@ class ProviderListPage extends BaseListPage {
align: "center",
filterMultiple: false,
filters: [
{text: "OAuth", value: "OAuth", children: Setting.getProviderTypeOptions("OAuth").map((o) => {return {text:o.id, value:o.name};})},
{text: "Email", value: "Email", children: Setting.getProviderTypeOptions("Email").map((o) => {return {text:o.id, value:o.name};})},
{text: "SMS", value: "SMS", children: Setting.getProviderTypeOptions("SMS").map((o) => {return {text:o.id, value:o.name};})},
{text: "Storage", value: "Storage", children: Setting.getProviderTypeOptions("Storage").map((o) => {return {text:o.id, value:o.name};})},
{text: "SAML", value: "SAML", children: Setting.getProviderTypeOptions("SAML").map((o) => {return {text:o.id, value:o.name};})},
{text: "Captcha", value: "Captcha", children: Setting.getProviderTypeOptions("Captcha").map((o) => {return {text:o.id, value:o.name};})},
{text: "OAuth", value: "OAuth", children: Setting.getProviderTypeOptions("OAuth").map((o) => {return {text: o.id, value: o.name};})},
{text: "Email", value: "Email", children: Setting.getProviderTypeOptions("Email").map((o) => {return {text: o.id, value: o.name};})},
{text: "SMS", value: "SMS", children: Setting.getProviderTypeOptions("SMS").map((o) => {return {text: o.id, value: o.name};})},
{text: "Storage", value: "Storage", children: Setting.getProviderTypeOptions("Storage").map((o) => {return {text: o.id, value: o.name};})},
{text: "SAML", value: "SAML", children: Setting.getProviderTypeOptions("SAML").map((o) => {return {text: o.id, value: o.name};})},
{text: "Captcha", value: "Captcha", children: Setting.getProviderTypeOptions("Captcha").map((o) => {return {text: o.id, value: o.name};})},
],
sorter: true,
render: (text, record, index) => {
return Provider.getProviderLogoWidget(record);
}
},
},
{
title: i18next.t("provider:Client ID"),
@@ -149,7 +149,7 @@ class ProviderListPage extends BaseListPage {
...this.getColumnSearchProps("clientId"),
render: (text, record, index) => {
return Setting.getShortText(text);
}
},
},
{
title: i18next.t("provider:Provider URL"),
@@ -166,7 +166,7 @@ class ProviderListPage extends BaseListPage {
}
</a>
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -186,7 +186,7 @@ class ProviderListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons";
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting";
import i18next from "i18next";
@@ -82,7 +82,7 @@ class ProviderTable extends React.Component {
}
</Select>
);
}
},
},
{
title: i18next.t("provider:Category"),
@@ -92,7 +92,7 @@ class ProviderTable extends React.Component {
render: (text, record, index) => {
const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
return provider?.category;
}
},
},
{
title: i18next.t("provider:Type"),
@@ -102,7 +102,7 @@ class ProviderTable extends React.Component {
render: (text, record, index) => {
const provider = Setting.getArrayItem(this.props.providers, "name", record.name);
return Provider.getProviderLogoWidget(provider);
}
},
},
{
title: i18next.t("provider:canSignUp"),
@@ -119,7 +119,7 @@ class ProviderTable extends React.Component {
this.updateField(table, index, "canSignUp", checked);
}} />
);
}
},
},
{
title: i18next.t("provider:canSignIn"),
@@ -136,7 +136,7 @@ class ProviderTable extends React.Component {
this.updateField(table, index, "canSignIn", checked);
}} />
);
}
},
},
{
title: i18next.t("provider:canUnlink"),
@@ -153,7 +153,7 @@ class ProviderTable extends React.Component {
this.updateField(table, index, "canUnlink", checked);
}} />
);
}
},
},
{
title: i18next.t("provider:prompted"),
@@ -170,7 +170,7 @@ class ProviderTable extends React.Component {
this.updateField(table, index, "prompted", checked);
}} />
);
}
},
},
// {
// title: i18next.t("provider:alertType"),
@@ -211,7 +211,7 @@ class ProviderTable extends React.Component {
</Tooltip>
</div>
);
}
},
},
];

View File

@@ -32,7 +32,7 @@ class RecordListPage extends BaseListPage {
return {
owner: "built-in",
name: "1234",
id : "1234",
id: "1234",
clientIp: "::1",
timestamp: moment().format(),
organization: "built-in",
@@ -74,7 +74,7 @@ class RecordListPage extends BaseListPage {
{text}
</a>
);
}
},
},
{
title: i18next.t("general:Timestamp"),
@@ -84,7 +84,7 @@ class RecordListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Organization"),
@@ -99,7 +99,7 @@ class RecordListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:User"),
@@ -114,7 +114,7 @@ class RecordListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Method"),
@@ -153,7 +153,7 @@ class RecordListPage extends BaseListPage {
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return text;
}
},
},
{
title: i18next.t("record:Is Triggered"),
@@ -170,7 +170,7 @@ class RecordListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
];

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {Button, Col, Modal, Row, Input} from "antd";
import {Button, Col, Input, Modal, Row} from "antd";
import i18next from "i18next";
import React from "react";
import * as Setting from "./Setting";

View File

@@ -99,7 +99,7 @@ class ResourceListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("resource:Application"),
@@ -114,7 +114,7 @@ class ResourceListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("resource:User"),
@@ -129,7 +129,7 @@ class ResourceListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("resource:Parent"),
@@ -155,7 +155,7 @@ class ResourceListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("resource:Tag"),
@@ -196,7 +196,7 @@ class ResourceListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFriendlyFileSize(text);
}
},
},
{
title: i18next.t("general:Preview"),
@@ -219,7 +219,7 @@ class ResourceListPage extends BaseListPage {
</div>
);
}
}
},
},
{
title: i18next.t("general:URL"),
@@ -238,7 +238,7 @@ class ResourceListPage extends BaseListPage {
</Button>
</div>
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -260,7 +260,7 @@ class ResourceListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -107,7 +107,7 @@ class RoleEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitRoleEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteRole()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -77,7 +77,7 @@ class RoleListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Name"),
@@ -93,7 +93,7 @@ class RoleListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -103,7 +103,7 @@ class RoleListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Display name"),
@@ -122,7 +122,7 @@ class RoleListPage extends BaseListPage {
...this.getColumnSearchProps("users"),
render: (text, record, index) => {
return Setting.getTags(text);
}
},
},
{
title: i18next.t("role:Sub roles"),
@@ -133,7 +133,7 @@ class RoleListPage extends BaseListPage {
...this.getColumnSearchProps("roles"),
render: (text, record, index) => {
return Setting.getTags(text);
}
},
},
{
title: i18next.t("general:Is enabled"),
@@ -145,7 +145,7 @@ class RoleListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -165,7 +165,7 @@ class RoleListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -14,7 +14,7 @@
import React from "react";
import * as Setting from "./Setting";
import {Menu, Dropdown} from "antd";
import {Dropdown, Menu} from "antd";
import {createFromIconfontCN} from "@ant-design/icons";
import "./App.less";

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
import {message, Tag, Tooltip} from "antd";
import {Tag, Tooltip, message} from "antd";
import {QuestionCircleTwoTone} from "@ant-design/icons";
import React from "react";
import {isMobile as isMobileDevice} from "react-device-detect";
@@ -75,35 +75,35 @@ export const OtherProviderInfo = {
},
"Azure Blob": {
logo: `${StaticBaseUrl}/img/social_azure.jpg`,
url: "https://azure.microsoft.com/en-us/services/storage/blobs/"
}
url: "https://azure.microsoft.com/en-us/services/storage/blobs/",
},
},
SAML: {
"Aliyun IDaaS": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/idaas"
url: "https://aliyun.com/product/idaas",
},
"Keycloak": {
logo: `${StaticBaseUrl}/img/social_keycloak.png`,
url: "https://www.keycloak.org/"
url: "https://www.keycloak.org/",
},
},
Payment: {
"Alipay": {
logo: `${StaticBaseUrl}/img/payment_alipay.png`,
url: "https://www.alipay.com/"
url: "https://www.alipay.com/",
},
"WeChat Pay": {
logo: `${StaticBaseUrl}/img/payment_wechat_pay.png`,
url: "https://pay.weixin.qq.com/"
url: "https://pay.weixin.qq.com/",
},
"PayPal": {
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
url: "https://www.paypal.com/"
url: "https://www.paypal.com/",
},
"GC": {
logo: `${StaticBaseUrl}/img/payment_gc.png`,
url: "https://gc.org"
url: "https://gc.org",
},
},
Captcha: {
@@ -122,8 +122,12 @@ export const OtherProviderInfo = {
"Aliyun Captcha": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
url: "https://help.aliyun.com/product/28308.html",
}
}
},
"GEETEST": {
logo: `${StaticBaseUrl}/img/social_geetest.png`,
url: "https://www.geetest.com",
},
},
};
export function getCountryRegionData() {
@@ -136,7 +140,7 @@ export function getCountryRegionData() {
countries.registerLocale(require("i18n-iso-countries/langs/" + language + ".json"));
var data = countries.getNames(language, {select: "official"});
var result = [];
for (var i in data) {result.push({code:i, name:data[i]});}
for (var i in data) {result.push({code: i, name: data[i]});}
return result;
}
@@ -301,7 +305,7 @@ export function isPromptAnswered(user, application) {
}
const providerItems = getAllPromptedProviderItems(application);
for (let i = 0; i < providerItems.length; i ++) {
for (let i = 0; i < providerItems.length; i++) {
if (!isProviderItemAnswered(user, application, providerItems[i])) {
return false;
}
@@ -428,7 +432,7 @@ export function getShortName(s) {
return s.split("/").slice(-1)[0];
}
export function getShortText(s, maxLength=35) {
export function getShortText(s, maxLength = 35) {
if (s.length > maxLength) {
return `${s.slice(0, maxLength)}...`;
} else {
@@ -445,13 +449,13 @@ export function getFriendlyFileSize(size) {
let num = (size / Math.pow(1024, i));
let round = Math.round(num);
num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round;
return `${num} ${"KMGTPEZY"[i-1]}B`;
return `${num} ${"KMGTPEZY"[i - 1]}B`;
}
function getRandomInt(s) {
let hash = 0;
if (s.length !== 0) {
for (let i = 0; i < s.length; i ++) {
for (let i = 0; i < s.length; i++) {
let char = s.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
@@ -594,7 +598,7 @@ export function getProviderTypeOptions(category) {
{id: "AWS S3", name: "AWS S3"},
{id: "Aliyun OSS", name: "Aliyun OSS"},
{id: "Tencent Cloud COS", name: "Tencent Cloud COS"},
{id: "Azure Blob", name: "Azure Blob"}
{id: "Azure Blob", name: "Azure Blob"},
]
);
} else if (category === "SAML") {
@@ -615,6 +619,7 @@ export function getProviderTypeOptions(category) {
{id: "reCAPTCHA", name: "reCAPTCHA"},
{id: "hCaptcha", name: "hCaptcha"},
{id: "Aliyun Captcha", name: "Aliyun Captcha"},
{id: "GEETEST", name: "GEETEST"},
]);
} else {
return [];
@@ -662,8 +667,8 @@ export function goToLogin(ths, application) {
return;
}
if (!application.enablePassword && window.location.pathname.includes("/signup/oauth/authorize")) {
const link = window.location.href.replace("/signup/oauth/authorize", "/login/oauth/authorize");
if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
const link = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
goToLink(link);
return;
}
@@ -685,7 +690,7 @@ export function goToSignup(ths, application) {
}
if (!application.enablePassword && window.location.pathname.includes("/login/oauth/authorize")) {
const link = window.location.href.replace("/login/oauth/authorize", "/signup/oauth/authorize");
const link = window.location.href.replace("/login/oauth/authorize", "/auto-signup/oauth/authorize");
goToLink(link);
return;
}
@@ -718,7 +723,7 @@ export function goToForget(ths, application) {
}
export function renderHelmet(application) {
if (application === undefined || application === null || application.organizationObj === undefined || application.organizationObj === null ||application.organizationObj === "") {
if (application === undefined || application === null || application.organizationObj === undefined || application.organizationObj === null || application.organizationObj === "") {
return null;
}
@@ -786,7 +791,7 @@ export function getDeduplicatedArray(array, filterArray, key) {
export function getNewRowNameForTable(table, rowName) {
const emptyCount = table.filter(row => row.name.includes(rowName)).length;
let res = rowName;
for (let i = 0; i < emptyCount; i ++) {
for (let i = 0; i < emptyCount; i++) {
res = res + " ";
}
return res;
@@ -847,86 +852,86 @@ export function getSyncerTableColumns(syncer) {
case "Keycloak":
return [
{
"name":"ID",
"type":"string",
"casdoorName":"Id",
"isHashed":true,
"values":[
"name": "ID",
"type": "string",
"casdoorName": "Id",
"isHashed": true,
"values": [
]
],
},
{
"name":"USERNAME",
"type":"string",
"casdoorName":"Name",
"isHashed":true,
"values":[
"name": "USERNAME",
"type": "string",
"casdoorName": "Name",
"isHashed": true,
"values": [
]
],
},
{
"name":"LAST_NAME+FIRST_NAME",
"type":"string",
"casdoorName":"DisplayName",
"isHashed":true,
"values":[
"name": "LAST_NAME+FIRST_NAME",
"type": "string",
"casdoorName": "DisplayName",
"isHashed": true,
"values": [
]
],
},
{
"name":"EMAIL",
"type":"string",
"casdoorName":"Email",
"isHashed":true,
"values":[
"name": "EMAIL",
"type": "string",
"casdoorName": "Email",
"isHashed": true,
"values": [
]
],
},
{
"name":"EMAIL_VERIFIED",
"type":"boolean",
"casdoorName":"EmailVerified",
"isHashed":true,
"values":[
"name": "EMAIL_VERIFIED",
"type": "boolean",
"casdoorName": "EmailVerified",
"isHashed": true,
"values": [
]
],
},
{
"name":"FIRST_NAME",
"type":"string",
"casdoorName":"FirstName",
"isHashed":true,
"values":[
"name": "FIRST_NAME",
"type": "string",
"casdoorName": "FirstName",
"isHashed": true,
"values": [
]
],
},
{
"name":"LAST_NAME",
"type":"string",
"casdoorName":"LastName",
"isHashed":true,
"values":[
"name": "LAST_NAME",
"type": "string",
"casdoorName": "LastName",
"isHashed": true,
"values": [
]
],
},
{
"name":"CREATED_TIMESTAMP",
"type":"string",
"casdoorName":"CreatedTime",
"isHashed":true,
"values":[
"name": "CREATED_TIMESTAMP",
"type": "string",
"casdoorName": "CreatedTime",
"isHashed": true,
"values": [
]
],
},
{
"name":"ENABLED",
"type":"boolean",
"casdoorName":"IsForbidden",
"isHashed":true,
"values":[
"name": "ENABLED",
"type": "boolean",
"casdoorName": "IsForbidden",
"isHashed": true,
"values": [
]
}
],
},
];
default:
return [];

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons";
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting";
import i18next from "i18next";
@@ -101,7 +101,7 @@ class SignupTable extends React.Component {
}
</Select>
);
}
},
},
{
title: i18next.t("provider:visible"),
@@ -123,7 +123,7 @@ class SignupTable extends React.Component {
}
}} />
);
}
},
},
{
title: i18next.t("provider:required"),
@@ -140,7 +140,7 @@ class SignupTable extends React.Component {
this.updateField(table, index, "required", checked);
}} />
);
}
},
},
{
title: i18next.t("provider:prompted"),
@@ -161,7 +161,7 @@ class SignupTable extends React.Component {
this.updateField(table, index, "prompted", checked);
}} />
);
}
},
},
{
title: i18next.t("application:rule"),
@@ -201,7 +201,7 @@ class SignupTable extends React.Component {
}
</Select>
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -221,7 +221,7 @@ class SignupTable extends React.Component {
</Tooltip>
</div>
);
}
},
},
];

View File

@@ -89,7 +89,7 @@ class SyncerEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitSyncerEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteSyncer()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -101,7 +101,7 @@ class SyncerListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Name"),
@@ -117,7 +117,7 @@ class SyncerListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -127,7 +127,7 @@ class SyncerListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("provider:Type"),
@@ -212,7 +212,7 @@ class SyncerListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -233,7 +233,7 @@ class SyncerListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons";
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Select, Switch, Table, Tooltip} from "antd";
import * as Setting from "./Setting";
import i18next from "i18next";
@@ -73,7 +73,7 @@ class SyncerTableColumnTable extends React.Component {
this.updateField(table, index, "name", e.target.value);
}} />
);
}
},
},
{
title: i18next.t("syncer:Column type"),
@@ -88,7 +88,7 @@ class SyncerTableColumnTable extends React.Component {
}
</Select>
);
}
},
},
{
title: i18next.t("syncer:Casdoor column"),
@@ -105,7 +105,7 @@ class SyncerTableColumnTable extends React.Component {
}
</Select>
);
}
},
},
{
title: i18next.t("syncer:Is hashed"),
@@ -117,7 +117,7 @@ class SyncerTableColumnTable extends React.Component {
this.updateField(table, index, "isHashed", checked);
}} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -137,7 +137,7 @@ class SyncerTableColumnTable extends React.Component {
</Tooltip>
</div>
);
}
},
},
];

View File

@@ -54,6 +54,6 @@ function testEmailProvider(provider, email = "") {
return fetch(`${Setting.ServerUrl}/api/send-email`, {
method: "POST",
credentials: "include",
body: JSON.stringify(emailForm)
body: JSON.stringify(emailForm),
}).then(res => res.json());
}

View File

@@ -68,7 +68,7 @@ class TokenEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitTokenEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteToken()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Name")}:

View File

@@ -81,7 +81,7 @@ class TokenListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -91,7 +91,7 @@ class TokenListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Application"),
@@ -106,7 +106,7 @@ class TokenListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Organization"),
@@ -121,7 +121,7 @@ class TokenListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:User"),
@@ -136,7 +136,7 @@ class TokenListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("token:Authorization code"),
@@ -147,7 +147,7 @@ class TokenListPage extends BaseListPage {
...this.getColumnSearchProps("code"),
render: (text, record, index) => {
return Setting.getClickable(text);
}
},
},
{
title: i18next.t("token:Access token"),
@@ -159,7 +159,7 @@ class TokenListPage extends BaseListPage {
...this.getColumnSearchProps("accessToken"),
render: (text, record, index) => {
return Setting.getClickable(text);
}
},
},
{
title: i18next.t("token:Expires in"),
@@ -202,7 +202,7 @@ class TokenListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined, LinkOutlined} from "@ant-design/icons";
import {DeleteOutlined, DownOutlined, LinkOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Table, Tooltip} from "antd";
import * as Setting from "./Setting";
import i18next from "i18next";
@@ -71,7 +71,7 @@ class UrlTable extends React.Component {
this.updateField(table, index, e.target.value);
}} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -91,7 +91,7 @@ class UrlTable extends React.Component {
</Tooltip>
</div>
);
}
},
},
];

View File

@@ -291,7 +291,7 @@ class UserEditPage extends React.Component {
}} />
</Col>
<Col span={11} >
{this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
{this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Email...")} destType={"email"} />) : null}
</Col>
</Row>
);
@@ -309,7 +309,7 @@ class UserEditPage extends React.Component {
}} />
</Col>
<Col span={11} >
{this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
{this.state.user.id === this.props.account?.id ? (<ResetModal application={this.state.application} disabled={disabled} buttonText={i18next.t("user:Reset Phone...")} destType={"phone"} />) : null}
</Col>
</Row>
);
@@ -427,6 +427,32 @@ class UserEditPage extends React.Component {
</Col>
</Row>
);
} else if (accountItem.name === "Roles") {
return (
<Row style={{marginTop: "20px", alignItems: "center"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Roles"), i18next.t("general:Roles - Tooltip"))} :
</Col>
<Col span={22} >
{
Setting.getTags(this.state.user.roles.map(role => role.name))
}
</Col>
</Row>
);
} else if (accountItem.name === "Permissions") {
return (
<Row style={{marginTop: "20px", alignItems: "center"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Permissions"), i18next.t("general:Permissions - Tooltip"))} :
</Col>
<Col span={22} >
{
Setting.getTags(this.state.user.permissions.map(permission => permission.name))
}
</Col>
</Row>
);
} else if (accountItem.name === "3rd-party logins") {
return (
!this.isSelfOrAdmin() ? null : (
@@ -440,7 +466,7 @@ class UserEditPage extends React.Component {
(this.state.application === null || this.state.user === null) ? null : (
this.state.application?.providers.filter(providerItem => Setting.isProviderVisible(providerItem)).map((providerItem, index) =>
(providerItem.provider.category === "OAuth") ? (
<OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} />
<OAuthWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} account={this.props.account} onUnlinked={() => {return this.unlinked();}} />
) : (
<SamlWidget key={providerItem.name} labelSpan={(Setting.isMobile()) ? 10 : 3} user={this.state.user} application={this.state.application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} />
)
@@ -541,7 +567,7 @@ class UserEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitUserEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteUser()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
{
this.state.application?.organizationObj.accountItems?.map(accountItem => {
return (

View File

@@ -152,7 +152,7 @@ class UserListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Application"),
@@ -168,7 +168,7 @@ class UserListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Name"),
@@ -184,7 +184,7 @@ class UserListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -194,7 +194,7 @@ class UserListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("general:Display name"),
@@ -215,7 +215,7 @@ class UserListPage extends BaseListPage {
<img src={text} alt={text} width={50} />
</a>
);
}
},
},
{
title: i18next.t("general:Email"),
@@ -230,7 +230,7 @@ class UserListPage extends BaseListPage {
{text}
</a>
);
}
},
},
{
title: i18next.t("general:Phone"),
@@ -281,7 +281,7 @@ class UserListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("user:Is global admin"),
@@ -293,7 +293,7 @@ class UserListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("user:Is forbidden"),
@@ -305,7 +305,7 @@ class UserListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("user:Is deleted"),
@@ -317,7 +317,7 @@ class UserListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -337,7 +337,7 @@ class UserListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -35,8 +35,8 @@ class WebAuthnCredentialTable extends React.Component {
{i18next.t("general:Delete")}
</Button>
);
}
}
},
},
];
return (

View File

@@ -84,8 +84,8 @@ const userTemplate = {
"phoneVerifiedTime": "",
"renameQuota": "3",
"tagline": "",
"website": ""
}
"website": "",
},
};
class WebhookEditPage extends React.Component {
@@ -155,7 +155,7 @@ class WebhookEditPage extends React.Component {
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitWebhookEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteWebhook()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} style={(Setting.isMobile())? {margin: "5px"}:{}} type="inner">
} style={(Setting.isMobile()) ? {margin: "5px"} : {}} type="inner">
<Row style={{marginTop: "10px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Organization"), i18next.t("general:Organization - Tooltip"))} :

View File

@@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from "@ant-design/icons";
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Table, Tooltip} from "antd";
import * as Setting from "./Setting";
import i18next from "i18next";
@@ -72,7 +72,7 @@ class WebhookHeaderTable extends React.Component {
this.updateField(table, index, "name", e.target.value);
}} />
);
}
},
},
{
title: i18next.t("webhook:Value"),
@@ -84,7 +84,7 @@ class WebhookHeaderTable extends React.Component {
this.updateField(table, index, "value", e.target.value);
}} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -104,7 +104,7 @@ class WebhookHeaderTable extends React.Component {
</Tooltip>
</div>
);
}
},
},
];

View File

@@ -80,7 +80,7 @@ class WebhookListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Name"),
@@ -96,7 +96,7 @@ class WebhookListPage extends BaseListPage {
{text}
</Link>
);
}
},
},
{
title: i18next.t("general:Created time"),
@@ -106,7 +106,7 @@ class WebhookListPage extends BaseListPage {
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
},
{
title: i18next.t("webhook:URL"),
@@ -123,7 +123,7 @@ class WebhookListPage extends BaseListPage {
}
</a>
);
}
},
},
{
title: i18next.t("webhook:Method"),
@@ -143,7 +143,7 @@ class WebhookListPage extends BaseListPage {
filters: [
{text: "application/json", value: "application/json"},
{text: "application/x-www-form-urlencoded", value: "application/x-www-form-urlencoded"},
]
],
},
{
title: i18next.t("webhook:Events"),
@@ -154,7 +154,7 @@ class WebhookListPage extends BaseListPage {
...this.getColumnSearchProps("events"),
render: (text, record, index) => {
return Setting.getTags(text);
}
},
},
{
title: i18next.t("webhook:Is user extended"),
@@ -166,7 +166,7 @@ class WebhookListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("general:Is enabled"),
@@ -178,7 +178,7 @@ class WebhookListPage extends BaseListPage {
return (
<Switch disabled checkedChildren="ON" unCheckedChildren="OFF" checked={text} />
);
}
},
},
{
title: i18next.t("general:Action"),
@@ -198,7 +198,7 @@ class WebhookListPage extends BaseListPage {
</Popconfirm>
</div>
);
}
},
},
];

View File

@@ -17,7 +17,7 @@ import {authConfig} from "./Auth";
export function getAccount(query) {
return fetch(`${authConfig.serverUrl}/api/get-account${query}`, {
method: "GET",
credentials: "include"
credentials: "include",
}).then(res => res.json());
}

View File

@@ -62,7 +62,7 @@ class ForgetPage extends React.Component {
} else {
Util.showMessage(
"error",
i18next.t("forget:Unknown forgot type: ") + this.state.type
i18next.t("forget:Unknown forget type: ") + this.state.type
);
}
}
@@ -96,7 +96,7 @@ class ForgetPage extends React.Component {
AuthBackend.getEmailAndPhone({
application: forms.step1.getFieldValue("application"),
organization: forms.step1.getFieldValue("organization"),
username: username
username: username,
}).then((res) => {
if (res.status === "ok") {
const phone = res.data.phone;
@@ -147,7 +147,7 @@ class ForgetPage extends React.Component {
name: this.state.name,
code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix,
type: "login"
type: "login",
}, oAuthParams).then(res => {
if (res.status === "ok") {
this.setState({current: 2, userId: res.data, username: res.data.split("/")[1]});
@@ -468,7 +468,7 @@ class ForgetPage extends React.Component {
</Form.Item>
<br />
<Form.Item hidden={this.state.current !== 2}>
<Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
<Button block type="primary" htmlType="submit" disabled={this.state.userId === ""}>
{i18next.t("forget:Change Password")}
</Button>
</Form.Item>

View File

@@ -20,35 +20,13 @@ import * as UserWebauthnBackend from "../backend/UserWebauthnBackend";
import * as AuthBackend from "./AuthBackend";
import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Provider from "./Provider";
import * as ProviderButton from "./ProviderButton";
import * as Util from "./Util";
import * as Setting from "../Setting";
import SelfLoginButton from "./SelfLoginButton";
import {GithubLoginButton, GoogleLoginButton} from "react-social-login-buttons";
import FacebookLoginButton from "./FacebookLoginButton";
import QqLoginButton from "./QqLoginButton";
import DingTalkLoginButton from "./DingTalkLoginButton";
import GiteeLoginButton from "./GiteeLoginButton";
import WechatLoginButton from "./WechatLoginButton";
import WeiboLoginButton from "./WeiboLoginButton";
import i18next from "i18next";
import LinkedInLoginButton from "./LinkedInLoginButton";
import WeComLoginButton from "./WeComLoginButton";
import LarkLoginButton from "./LarkLoginButton";
import GitLabLoginButton from "./GitLabLoginButton";
import AdfsLoginButton from "./AdfsLoginButton";
import BaiduLoginButton from "./BaiduLoginButton";
import AlipayLoginButton from "./AlipayLoginButton";
import CasdoorLoginButton from "./CasdoorLoginButton";
import InfoflowLoginButton from "./InfoflowLoginButton";
import AppleLoginButton from "./AppleLoginButton";
import AzureADLoginButton from "./AzureADLoginButton";
import SlackLoginButton from "./SlackLoginButton";
import SteamLoginButton from "./SteamLoginButton";
import OktaLoginButton from "./OktaLoginButton";
import DouyinLoginButton from "./DouyinLoginButton";
import CustomGithubCorner from "../CustomGithubCorner";
import {CountDownInput} from "../common/CountDownInput";
import BilibiliLoginButton from "./BilibiliLoginButton";
const {TabPane} = Tabs;
@@ -59,7 +37,7 @@ class LoginPage extends React.Component {
classes: props,
type: props.type,
applicationName: props.applicationName !== undefined ? props.applicationName : (props.match === undefined ? null : props.match.params.applicationName),
owner : props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner),
owner: props.owner !== undefined ? props.owner : (props.match === undefined ? null : props.match.params.owner),
application: null,
mode: props.mode !== undefined ? props.mode : (props.match === undefined ? null : props.match.params.mode), // "signup" or "signin"
isCodeSignin: false,
@@ -68,7 +46,7 @@ class LoginPage extends React.Component {
validEmailOrPhone: false,
validEmail: false,
validPhone: false,
loginMethod: "password"
loginMethod: "password",
};
if (this.state.type === "cas" && props.match?.params.casApplicationName !== undefined) {
@@ -146,6 +124,16 @@ class LoginPage extends React.Component {
}
onFinish(values) {
if (this.state.loginMethod === "webAuthn") {
let username = this.state.username;
if (username === null || username === "") {
username = values["username"];
}
this.signInWithWebAuthn(username);
return;
}
const application = this.getApplicationObj();
const ths = this;
@@ -191,6 +179,10 @@ class LoginPage extends React.Component {
values["type"] = "saml";
}
if (this.state.owner != null) {
values["organization"] = this.state.owner;
}
AuthBackend.login(values, oAuthParams)
.then((res) => {
if (res.status === "ok") {
@@ -203,6 +195,7 @@ class LoginPage extends React.Component {
} else if (responseType === "code") {
const code = res.data;
const concatChar = oAuthParams?.redirectUri?.includes("?") ? "&" : "?";
const noRedirect = oAuthParams.noRedirect;
if (Setting.hasPromptPage(application)) {
AuthBackend.getAccount("")
@@ -224,7 +217,19 @@ class LoginPage extends React.Component {
}
});
} else {
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
if (noRedirect === "true") {
window.close();
const newWindow = window.open(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
if (newWindow) {
setInterval(() => {
if (!newWindow.closed) {
newWindow.close();
}
}, 1000);
}
} else {
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
}
}
// Util.showMessage("success", `Authorization code: ${res.data}`);
@@ -243,61 +248,6 @@ class LoginPage extends React.Component {
}
}
getSigninButton(type) {
const text = i18next.t("login:Sign in with {type}").replace("{type}", type);
if (type === "GitHub") {
return <GithubLoginButton text={text} align={"center"} />;
} else if (type === "Google") {
return <GoogleLoginButton text={text} align={"center"} />;
} else if (type === "QQ") {
return <QqLoginButton text={text} align={"center"} />;
} else if (type === "Facebook") {
return <FacebookLoginButton text={text} align={"center"} />;
} else if (type === "Weibo") {
return <WeiboLoginButton text={text} align={"center"} />;
} else if (type === "Gitee") {
return <GiteeLoginButton text={text} align={"center"} />;
} else if (type === "WeChat") {
return <WechatLoginButton text={text} align={"center"} />;
} else if (type === "DingTalk") {
return <DingTalkLoginButton text={text} align={"center"} />;
} else if (type === "LinkedIn") {
return <LinkedInLoginButton text={text} align={"center"} />;
} else if (type === "WeCom") {
return <WeComLoginButton text={text} align={"center"} />;
} else if (type === "Lark") {
return <LarkLoginButton text={text} align={"center"} />;
} else if (type === "GitLab") {
return <GitLabLoginButton text={text} align={"center"} />;
} else if (type === "Adfs") {
return <AdfsLoginButton text={text} align={"center"} />;
} else if (type === "Casdoor") {
return <CasdoorLoginButton text={text} align={"center"} />;
} else if (type === "Baidu") {
return <BaiduLoginButton text={text} align={"center"} />;
} else if (type === "Alipay") {
return <AlipayLoginButton text={text} align={"center"} />;
} else if (type === "Infoflow") {
return <InfoflowLoginButton text={text} align={"center"} />;
} else if (type === "Apple") {
return <AppleLoginButton text={text} align={"center"} />;
} else if (type === "AzureAD") {
return <AzureADLoginButton text={text} align={"center"} />;
} else if (type === "Slack") {
return <SlackLoginButton text={text} align={"center"} />;
} else if (type === "Steam") {
return <SteamLoginButton text={text} align={"center"} />;
} else if (type === "Bilibili") {
return <BilibiliLoginButton text={text} align={"center"} />;
} else if (type === "Okta") {
return <OktaLoginButton text={text} align={"center"} />;
} else if (type === "Douyin") {
return <DouyinLoginButton text={text} align={"center"} />;
}
return text;
}
getSamlUrl(provider) {
const params = new URLSearchParams(this.props.location.search);
let clientId = params.get("client_id");
@@ -315,35 +265,6 @@ class LoginPage extends React.Component {
});
}
renderProviderLogo(provider, application, width, margin, size) {
if (size === "small") {
if (provider.category === "OAuth") {
return (
<a key={provider.displayName} href={Provider.getAuthUrl(application, provider, "signup")}>
<img width={width} height={width} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
</a>
);
} else if (provider.category === "SAML") {
return (
<a key={provider.displayName} onClick={this.getSamlUrl.bind(this, provider)}>
<img width={width} height={width} src={Setting.getProviderLogoURL(provider)} alt={provider.displayName} style={{margin: margin}} />
</a>
);
}
} else {
return (
<div key={provider.displayName} style={{marginBottom: "10px"}}>
<a href={Provider.getAuthUrl(application, provider, "signup")}>
{
this.getSigninButton(provider.type)
}
</a>
</div>
);
}
}
isProviderVisible(providerItem) {
if (this.state.mode === "signup") {
return Setting.isProviderVisibleForSignUp(providerItem);
@@ -370,7 +291,7 @@ class LoginPage extends React.Component {
<Button type="primary" key="signin">
Sign In
</Button>
</Link>
</Link>,
]}
>
</Result>
@@ -418,7 +339,7 @@ class LoginPage extends React.Component {
rules={[
{
required: true,
message: i18next.t("login:Please input your username, Email or phone!")
message: i18next.t("login:Please input your username, Email or phone!"),
},
{
validator: (_, value) => {
@@ -438,8 +359,8 @@ class LoginPage extends React.Component {
this.setState({validEmailOrPhone: true});
return Promise.resolve();
}
}
},
},
]}
>
<Input
@@ -482,19 +403,24 @@ class LoginPage extends React.Component {
</Button>
) :
(
<Button type="primary" style={{width: "100%", marginBottom: "5px"}} onClick={() => this.signInWithWebAuthn()}>
<Button
type="primary"
htmlType="submit"
style={{width: "100%", marginBottom: "5px"}}
disabled={!application.enablePassword}
>
{i18next.t("login:Sign in with WebAuthn")}
</Button>
)
}
{
!application.enableSignUp ? null : this.renderFooter(application)
this.renderFooter(application)
}
</Form.Item>
<Form.Item>
{
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
return this.renderProviderLogo(providerItem.provider, application, 30, 5, "small");
return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small");
})
}
</Form.Item>
@@ -515,19 +441,15 @@ class LoginPage extends React.Component {
<br />
{
application.providers.filter(providerItem => this.isProviderVisible(providerItem)).map(providerItem => {
return this.renderProviderLogo(providerItem.provider, application, 40, 10, "big");
return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big");
})
}
{
!application.enableSignUp ? null : (
<div>
<br />
{
this.renderFooter(application)
}
</div>
)
}
<div>
<br />
{
this.renderFooter(application)
}
</div>
</div>
);
}
@@ -562,13 +484,19 @@ class LoginPage extends React.Component {
}
</span>
<span style={{float: "right"}}>
{i18next.t("login:No account?")}&nbsp;
<a onClick={() => {
sessionStorage.setItem("signinUrl", window.location.href);
Setting.goToSignup(this, application);
}}>
{i18next.t("login:sign up now")}
</a>
{
!application.enableSignUp ? null : (
<>
{i18next.t("login:No account?")}&nbsp;
<a onClick={() => {
sessionStorage.setItem("signinUrl", window.location.href);
Setting.goToSignup(this, application);
}}>
{i18next.t("login:sign up now")}
</a>
</>
)
}
</span>
</React.Fragment>
);
@@ -620,16 +548,16 @@ class LoginPage extends React.Component {
);
}
signInWithWebAuthn() {
if (this.state.username === null || this.state.username === "") {
signInWithWebAuthn(username) {
if (username === null || username === "") {
Setting.showMessage("error", "username is required for webauthn login");
return;
}
let application = this.getApplicationObj();
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/begin?owner=${application.organization}&name=${this.state.username}`, {
return fetch(`${Setting.ServerUrl}/api/webauthn/signin/begin?owner=${application.organization}&name=${username}`, {
method: "GET",
credentials: "include"
credentials: "include",
})
.then(res => res.json())
.then((credentialRequestOptions) => {
@@ -644,7 +572,7 @@ class LoginPage extends React.Component {
});
return navigator.credentials.get({
publicKey: credentialRequestOptions.publicKey
publicKey: credentialRequestOptions.publicKey,
});
})
.then((assertion) => {
@@ -666,7 +594,7 @@ class LoginPage extends React.Component {
signature: UserWebauthnBackend.webAuthnBufferEncode(sig),
userHandle: UserWebauthnBackend.webAuthnBufferEncode(userHandle),
},
})
}),
})
.then(res => res.json()).then((res) => {
if (res.msg === "") {
@@ -692,7 +620,7 @@ class LoginPage extends React.Component {
>
<CountDownInput
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
onButtonClickArgs={[this.state.username, "", Setting.getApplicationOrgName(application), true]}
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationName(application)]}
/>
</Form.Item>
) : (

View File

@@ -117,7 +117,7 @@ class PromptPage extends React.Component {
<div>
{
(application === null || this.state.user === null) ? null : (
application?.providers.filter(providerItem => Setting.isProviderPrompted(providerItem)).map((providerItem, index) => <OAuthWidget key={providerItem.name} labelSpan={6} user={this.state.user} application={application} providerItem={providerItem} onUnlinked={() => {return this.unlinked();}} />)
application?.providers.filter(providerItem => Setting.isProviderPrompted(providerItem)).map((providerItem, index) => <OAuthWidget key={providerItem.name} labelSpan={6} user={this.state.user} application={application} providerItem={providerItem} account={this.props.account} onUnlinked={() => {return this.unlinked();}} />)
)
}
</div>
@@ -202,7 +202,7 @@ class PromptPage extends React.Component {
Setting.goToLogin(this, application);
}}>
Sign In
</Button>
</Button>,
]}
>
</Result>

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