mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-06 21:08:50 +08:00
Compare commits
44 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
cba338eef2 | ||
![]() |
c428de6e42 | ||
![]() |
9bca6bb72e | ||
![]() |
cd966116d4 | ||
![]() |
9abf1b9d73 | ||
![]() |
6aaba6debd | ||
![]() |
77565712e0 | ||
![]() |
d025259db7 | ||
![]() |
aafdc546fa | ||
![]() |
539ca2d731 | ||
![]() |
ea326b3513 | ||
![]() |
98ef766fb4 | ||
![]() |
e94ada9ea2 | ||
![]() |
4ea482223d | ||
![]() |
d55ae7d1d2 | ||
![]() |
d72e00605f | ||
![]() |
be74cb621f | ||
![]() |
13404d6035 | ||
![]() |
afa9c530ad | ||
![]() |
1600615aca | ||
![]() |
2bb8491499 | ||
![]() |
293283ed25 | ||
![]() |
9cb519d1e9 | ||
![]() |
fb9b8f1662 | ||
![]() |
2fec3f72ae | ||
![]() |
11695220a8 | ||
![]() |
155660b0d7 | ||
![]() |
1c72f5300c | ||
![]() |
3dd56195d9 | ||
![]() |
8865244262 | ||
![]() |
3400fa1e9c | ||
![]() |
bdc5c92ef0 | ||
![]() |
4e3eedf246 | ||
![]() |
8e98fc5a9f | ||
![]() |
6f6159be07 | ||
![]() |
3e4dbc2dcb | ||
![]() |
48b5b27982 | ||
![]() |
1839252c30 | ||
![]() |
1fff1db6a7 | ||
![]() |
a0b0e186b7 | ||
![]() |
8c7f235ee1 | ||
![]() |
a0a762aa6f | ||
![]() |
2eec53a6d0 | ||
![]() |
117dec4542 |
@@ -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
82
captcha/geetest.go
Normal 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)
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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()
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
2
go.mod
@@ -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
2
go.sum
@@ -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=
|
||||
|
@@ -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))
|
||||
|
@@ -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 {
|
||||
|
@@ -139,7 +139,7 @@
|
||||
"cryptoAlgorithm": "RS256",
|
||||
"bitSize": 4096,
|
||||
"expireInYears": 20,
|
||||
"publicKey": "",
|
||||
"certificate": "",
|
||||
"privateKey": ""
|
||||
}
|
||||
],
|
||||
|
@@ -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"
|
||||
)
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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"},
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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, ""
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 := ""
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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 {
|
||||
|
@@ -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))
|
||||
|
@@ -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()
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -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")
|
||||
|
@@ -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"
|
||||
|
@@ -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
|
||||
|
@@ -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))
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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",
|
||||
|
@@ -34,7 +34,7 @@ module.exports = {
|
||||
"/cas/validate": {
|
||||
target: "http://localhost:8000",
|
||||
changeOrigin: true,
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
|
@@ -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"
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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} />)} />
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -30,6 +30,7 @@ class BaseListPage extends React.Component {
|
||||
loading: false,
|
||||
searchText: "",
|
||||
searchedColumn: "",
|
||||
isAuthorized: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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} />
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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";
|
||||
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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) => {
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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"))} :
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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"))} :
|
||||
|
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -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";
|
||||
|
@@ -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"))} :
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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>)
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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"))} :
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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 => {
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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} />
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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";
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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"))} :
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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";
|
||||
|
||||
|
@@ -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 [];
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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"))} :
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
|
@@ -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")}:
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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 (
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -35,8 +35,8 @@ class WebAuthnCredentialTable extends React.Component {
|
||||
{i18next.t("general:Delete")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
|
@@ -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"))} :
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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>
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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?")}
|
||||
<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?")}
|
||||
<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>
|
||||
) : (
|
||||
|
@@ -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
Reference in New Issue
Block a user