Compare commits

...

15 Commits

Author SHA1 Message Date
d1f88ca9b8 feat: support google one tap signin (#2131)
* feat: add google one tap support

* feat: gofumpt

* feat: add google provider rule conf

* feat: update i18n
2023-07-25 15:49:15 +08:00
bfe8e5f3e7 fix: fix response data assignment error (#2129) 2023-07-25 13:52:31 +08:00
702ee6acd0 Print log for StartLdapServer()'s error 2023-07-25 01:49:43 +08:00
0a9587901a fix: fix response data assignment error in ApplicationEditPage.js (#2126) 2023-07-24 20:09:09 +08:00
577bd6ce58 feat: fix response data assignment error (#2123) 2023-07-24 14:52:30 +08:00
3c4112dd44 refactor: optimize the code to getEnforcer (#2120) 2023-07-24 14:02:34 +08:00
b7a37126ad feat: restrict redirectUrls for CAS login (#2118)
* feat: support cas restricted login

* feat: add cas login i18n

* feat: add CheckCasService for all cas api

* feat: gofumpt

* feat: replace 404

* feat: reuse i18n

* feat: delete CheckCasService

* Update token_cas.go

* Update LoginPage.js

* Update token_cas.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-24 11:47:31 +08:00
8669d5bb0d chore: hide field of IntranetEndpoint in Tencent COS storage provider (#2117) 2023-07-23 19:02:42 +08:00
aee3ea4981 feat: improve TermsOfUse UI in mobile (#2106)
* style: Mobile interface adaptation

Signed-off-by: baihhh <2542274498@qq.com>

* Update index.css

---------

Signed-off-by: baihhh <2542274498@qq.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-23 15:28:13 +08:00
516f4b7569 Fix response of /api/get-sorted-users and /api/get-user-count 2023-07-23 14:46:38 +08:00
7d7ca10481 fix: hide fields of minio storage provider (#2115)
* feat: hide field of minio storage provider

* feat: hide field of domain in minio storage provider
2023-07-23 14:40:30 +08:00
a9d4978a0f chore: hide fields of local file system storage provider (#2109)
* style: adjust local file system storage

* style: disable domain when use local file system
2023-07-23 11:48:15 +08:00
09f40bb5ce Fix id of "/api/get-resource" API 2023-07-23 11:33:48 +08:00
a6f803aff1 feat: refactor code to use responseOK everywhere (#2111)
* refactor: use responseOK return frontend format json data

* revert handle error

* revert handle error
2023-07-23 09:49:16 +08:00
fc9528be43 Add createDatabaseForPostgres() 2023-07-22 16:19:13 +08:00
81 changed files with 564 additions and 361 deletions

View File

@ -48,14 +48,11 @@ func (c *ApiController) GetApplications() {
} else {
applications, err = object.GetOrganizationApplications(owner, organization)
}
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
c.ResponseOk(object.GetMaskedApplications(applications, userId))
} else {
limit := util.ParseInt(limit)
count, err := object.GetApplicationCount(owner, field, value)
@ -86,14 +83,14 @@ func (c *ApiController) GetApplications() {
func (c *ApiController) GetApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
app, err := object.GetApplication(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetMaskedApplication(app, userId)
c.ServeJSON()
c.ResponseOk(object.GetMaskedApplication(app, userId))
}
// GetUserApplication
@ -106,25 +103,24 @@ func (c *ApiController) GetApplication() {
func (c *ApiController) GetUserApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
user, err := object.GetUser(id)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
return
}
app, err := object.GetApplicationByUser(user)
application, err := object.GetApplicationByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetMaskedApplication(app, userId)
c.ServeJSON()
c.ResponseOk(object.GetMaskedApplication(application, userId))
}
// GetOrganizationApplications
@ -157,8 +153,7 @@ func (c *ApiController) GetOrganizationApplications() {
return
}
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
c.ResponseOk(object.GetMaskedApplications(applications, userId))
} else {
limit := util.ParseInt(limit)

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetCerts() {
return
}
c.Data["json"] = maskedCerts
c.ServeJSON()
c.ResponseOk(maskedCerts)
} else {
limit := util.ParseInt(limit)
count, err := object.GetCertCount(owner, field, value)
@ -87,8 +86,7 @@ func (c *ApiController) GetGlobleCerts() {
return
}
c.Data["json"] = maskedCerts
c.ServeJSON()
c.ResponseOk(maskedCerts)
} else {
limit := util.ParseInt(limit)
count, err := object.GetGlobalCertsCount(field, value)
@ -123,8 +121,7 @@ func (c *ApiController) GetCert() {
return
}
c.Data["json"] = object.GetMaskedCert(cert)
c.ServeJSON()
c.ResponseOk(object.GetMaskedCert(cert))
}
// UpdateCert

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetChats() {
return
}
c.Data["json"] = maskedChats
c.ServeJSON()
c.ResponseOk(maskedChats)
} else {
limit := util.ParseInt(limit)
count, err := object.GetChatCount(owner, field, value)
@ -82,8 +81,7 @@ func (c *ApiController) GetChat() {
return
}
c.Data["json"] = maskedChat
c.ServeJSON()
c.ResponseOk(maskedChat)
}
// UpdateChat

View File

@ -82,9 +82,9 @@ func (c *ApiController) GetGroup() {
group, err := object.GetGroup(id)
if err != nil {
c.ResponseError(err.Error())
} else {
c.ResponseOk(group)
return
}
c.ResponseOk(group)
}
// UpdateGroup

View File

@ -125,7 +125,12 @@ func (c *ApiController) GetLdap() {
}
_, name := util.GetOwnerAndNameFromId(id)
c.ResponseOk(object.GetMaskedLdap(object.GetLdap(name)))
ldap, err := object.GetLdap(name)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedLdap(ldap))
}
// AddLdap

View File

@ -57,8 +57,7 @@ func (c *ApiController) GetMessages() {
return
}
c.Data["json"] = object.GetMaskedMessages(messages)
c.ServeJSON()
c.ResponseOk(object.GetMaskedMessages(messages))
} else {
limit := util.ParseInt(limit)
count, err := object.GetMessageCount(owner, organization, field, value)
@ -94,8 +93,7 @@ func (c *ApiController) GetMessage() {
return
}
c.Data["json"] = object.GetMaskedMessage(message)
c.ServeJSON()
c.ResponseOk(message)
}
func (c *ApiController) ResponseErrorStream(errorText string) {

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetModels() {
return
}
c.Data["json"] = models
c.ServeJSON()
c.ResponseOk(models)
} else {
limit := util.ParseInt(limit)
count, err := object.GetModelCount(owner, field, value)
@ -82,8 +81,7 @@ func (c *ApiController) GetModel() {
return
}
c.Data["json"] = model
c.ServeJSON()
c.ResponseOk(model)
}
// UpdateModel

View File

@ -55,8 +55,7 @@ func (c *ApiController) GetOrganizations() {
return
}
c.Data["json"] = maskedOrganizations
c.ServeJSON()
c.ResponseOk(maskedOrganizations)
} else {
if !isGlobalAdmin {
maskedOrganizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
@ -184,6 +183,8 @@ func (c *ApiController) DeleteOrganization() {
func (c *ApiController) GetDefaultApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
redirectUri := c.Input().Get("redirectUri")
typ := c.Input().Get("type")
application, err := object.GetDefaultApplication(id)
if err != nil {
@ -191,6 +192,14 @@ func (c *ApiController) GetDefaultApplication() {
return
}
if typ == "cas" {
err = object.CheckCasRestrict(application, c.GetAcceptLanguage(), redirectUri)
if err != nil {
c.ResponseError(err.Error())
return
}
}
maskedApplication := object.GetMaskedApplication(application, userId)
c.ResponseOk(maskedApplication)
}

View File

@ -46,8 +46,7 @@ func (c *ApiController) GetPayments() {
return
}
c.Data["json"] = payments
c.ServeJSON()
c.ResponseOk(payments)
} else {
limit := util.ParseInt(limit)
count, err := object.GetPaymentCount(owner, organization, field, value)
@ -106,8 +105,7 @@ func (c *ApiController) GetPayment() {
return
}
c.Data["json"] = payment
c.ServeJSON()
c.ResponseOk(payment)
}
// UpdatePayment

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetPermissions() {
return
}
c.Data["json"] = permissions
c.ServeJSON()
c.ResponseOk(permissions)
} else {
limit := util.ParseInt(limit)
count, err := object.GetPermissionCount(owner, field, value)
@ -85,7 +84,6 @@ func (c *ApiController) GetPermissionsBySubmitter() {
}
c.ResponseOk(permissions, len(permissions))
return
}
// GetPermissionsByRole
@ -104,7 +102,6 @@ func (c *ApiController) GetPermissionsByRole() {
}
c.ResponseOk(permissions, len(permissions))
return
}
// GetPermission
@ -123,8 +120,7 @@ func (c *ApiController) GetPermission() {
return
}
c.Data["json"] = permission
c.ServeJSON()
c.ResponseOk(permission)
}
// UpdatePermission

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetPlans() {
return
}
c.Data["json"] = plans
c.ServeJSON()
c.ResponseOk(plans)
} else {
limit := util.ParseInt(limit)
count, err := object.GetPlanCount(owner, field, value)
@ -95,11 +94,10 @@ func (c *ApiController) GetPlan() {
plan.Options = append(plan.Options, option.DisplayName)
}
c.Data["json"] = plan
c.ResponseOk(plan)
} else {
c.Data["json"] = plan
c.ResponseOk(plan)
}
c.ServeJSON()
}
// UpdatePlan

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetPricings() {
return
}
c.Data["json"] = pricings
c.ServeJSON()
c.ResponseOk(pricings)
} else {
limit := util.ParseInt(limit)
count, err := object.GetPricingCount(owner, field, value)
@ -82,8 +81,7 @@ func (c *ApiController) GetPricing() {
return
}
c.Data["json"] = pricing
c.ServeJSON()
c.ResponseOk(pricing)
}
// UpdatePricing

View File

@ -46,8 +46,7 @@ func (c *ApiController) GetProducts() {
return
}
c.Data["json"] = products
c.ServeJSON()
c.ResponseOk(products)
} else {
limit := util.ParseInt(limit)
count, err := object.GetProductCount(owner, field, value)
@ -89,8 +88,7 @@ func (c *ApiController) GetProduct() {
return
}
c.Data["json"] = product
c.ServeJSON()
c.ResponseOk(product)
}
// UpdateProduct

View File

@ -51,8 +51,7 @@ func (c *ApiController) GetRecords() {
return
}
c.Data["json"] = records
c.ServeJSON()
c.ResponseOk(records)
} else {
limit := util.ParseInt(limit)
if c.IsGlobalAdmin() && organizationName != "" {
@ -99,8 +98,7 @@ func (c *ApiController) GetRecordsByFilter() {
return
}
c.Data["json"] = records
c.ServeJSON()
c.ResponseOk(records)
}
// AddRecord

View File

@ -67,8 +67,7 @@ func (c *ApiController) GetResources() {
return
}
c.Data["json"] = resources
c.ServeJSON()
c.ResponseOk(resources)
} else {
limit := util.ParseInt(limit)
count, err := object.GetResourceCount(owner, user, field, value)
@ -104,8 +103,7 @@ func (c *ApiController) GetResource() {
return
}
c.Data["json"] = resource
c.ServeJSON()
c.ResponseOk(resource)
}
// UpdateResource

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetRoles() {
return
}
c.Data["json"] = roles
c.ServeJSON()
c.ResponseOk(roles)
} else {
limit := util.ParseInt(limit)
count, err := object.GetRoleCount(owner, field, value)
@ -82,8 +81,7 @@ func (c *ApiController) GetRole() {
return
}
c.Data["json"] = role
c.ServeJSON()
c.ResponseOk(role)
}
// UpdateRole

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetSessions() {
return
}
c.Data["json"] = sessions
c.ServeJSON()
c.ResponseOk(sessions)
} else {
limit := util.ParseInt(limit)
count, err := object.GetSessionCount(owner, field, value)
@ -81,8 +80,7 @@ func (c *ApiController) GetSingleSession() {
return
}
c.Data["json"] = session
c.ServeJSON()
c.ResponseOk(session)
}
// UpdateSession
@ -161,7 +159,5 @@ func (c *ApiController) IsSessionDuplicated() {
return
}
c.Data["json"] = &Response{Status: "ok", Msg: "", Data: isUserSessionDuplicated}
c.ServeJSON()
c.ResponseOk(isUserSessionDuplicated)
}

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetSubscriptions() {
return
}
c.Data["json"] = subscriptions
c.ServeJSON()
c.ResponseOk(subscriptions)
} else {
limit := util.ParseInt(limit)
count, err := object.GetSubscriptionCount(owner, field, value)
@ -82,8 +81,7 @@ func (c *ApiController) GetSubscription() {
return
}
c.Data["json"] = subscription
c.ServeJSON()
c.ResponseOk(subscription)
}
// UpdateSubscription

View File

@ -46,8 +46,7 @@ func (c *ApiController) GetSyncers() {
return
}
c.Data["json"] = organizationSyncers
c.ServeJSON()
c.ResponseOk(organizationSyncers)
} else {
limit := util.ParseInt(limit)
count, err := object.GetSyncerCount(owner, organization, field, value)
@ -83,8 +82,7 @@ func (c *ApiController) GetSyncer() {
return
}
c.Data["json"] = syncer
c.ServeJSON()
c.ResponseOk(syncer)
}
// UpdateSyncer

View File

@ -47,8 +47,7 @@ func (c *ApiController) GetTokens() {
return
}
c.Data["json"] = token
c.ServeJSON()
c.ResponseOk(token)
} else {
limit := util.ParseInt(limit)
count, err := object.GetTokenCount(owner, organization, field, value)
@ -83,8 +82,7 @@ func (c *ApiController) GetToken() {
return
}
c.Data["json"] = token
c.ServeJSON()
c.ResponseOk(token)
}
// UpdateToken

View File

@ -45,8 +45,7 @@ func (c *ApiController) GetGlobalUsers() {
return
}
c.Data["json"] = maskedUsers
c.ServeJSON()
c.ResponseOk(maskedUsers)
} else {
limit := util.ParseInt(limit)
count, err := object.GetGlobalUserCount(field, value)
@ -106,8 +105,7 @@ func (c *ApiController) GetUsers() {
return
}
c.Data["json"] = maskedUsers
c.ServeJSON()
c.ResponseOk(maskedUsers)
} else {
limit := util.ParseInt(limit)
count, err := object.GetUserCount(owner, field, value, groupName)
@ -215,8 +213,7 @@ func (c *ApiController) GetUser() {
return
}
c.Data["json"] = maskedUser
c.ServeJSON()
c.ResponseOk(maskedUser)
}
// UpdateUser
@ -513,8 +510,7 @@ func (c *ApiController) GetSortedUsers() {
return
}
c.Data["json"] = maskedUsers
c.ServeJSON()
c.ResponseOk(maskedUsers)
}
// GetUserCount
@ -541,8 +537,7 @@ func (c *ApiController) GetUserCount() {
return
}
c.Data["json"] = count
c.ServeJSON()
c.ResponseOk(count)
}
// AddUserkeys

View File

@ -46,8 +46,7 @@ func (c *ApiController) GetWebhooks() {
return
}
c.Data["json"] = webhooks
c.ServeJSON()
c.ResponseOk(webhooks)
} else {
limit := util.ParseInt(limit)
count, err := object.GetWebhookCount(owner, organization, field, value)
@ -84,8 +83,7 @@ func (c *ApiController) GetWebhook() {
return
}
c.Data["json"] = webhook
c.ServeJSON()
c.ResponseOk(webhook)
}
// UpdateWebhook

View File

@ -21,15 +21,39 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/casdoor/casdoor/util"
"golang.org/x/oauth2"
)
const GoogleIdTokenKey = "GoogleIdToken"
type GoogleIdProvider struct {
Client *http.Client
Config *oauth2.Config
}
// https://developers.google.com/identity/sign-in/web/backend-auth#calling-the-tokeninfo-endpoint
type GoogleIdToken struct {
// These six fields are included in all Google ID Tokens.
Iss string `json:"iss"` // The issuer, or signer, of the token. For Google-signed ID tokens, this value is https://accounts.google.com.
Sub string `json:"sub"` // The subject: the ID that represents the principal making the request.
Azp string `json:"azp"` // Optional. Who the token was issued to. Here is the ClientID
Aud string `json:"aud"` // The audience of the token. Here is the ClientID
Iat string `json:"iat"` // Unix epoch time when the token was issued.
Exp string `json:"exp"` // Unix epoch time when the token expires.
// These seven fields are only included when the user has granted the "profile" and "email" OAuth scopes to the application.
Email string `json:"email"`
EmailVerified string `json:"email_verified"`
Name string `json:"name"`
Picture string `json:"picture"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Locale string `json:"locale"`
}
func NewGoogleIdProvider(clientId string, clientSecret string, redirectUrl string) *GoogleIdProvider {
idp := &GoogleIdProvider{}
@ -61,6 +85,25 @@ func (idp *GoogleIdProvider) getConfig() *oauth2.Config {
}
func (idp *GoogleIdProvider) GetToken(code string) (*oauth2.Token, error) {
// Obtained the GoogleIdToken through Google OneTap authorization.
if strings.HasPrefix(code, GoogleIdTokenKey) {
code = strings.TrimPrefix(code, GoogleIdTokenKey+"-")
var googleIdToken GoogleIdToken
if err := json.Unmarshal([]byte(code), &googleIdToken); err != nil {
return nil, err
}
expiry := int64(util.ParseInt(googleIdToken.Exp))
token := &oauth2.Token{
AccessToken: fmt.Sprintf("%v-%v", GoogleIdTokenKey, googleIdToken.Sub),
TokenType: "Bearer",
Expiry: time.Unix(expiry, 0),
}
token = token.WithExtra(map[string]interface{}{
GoogleIdTokenKey: googleIdToken,
})
return token, nil
}
ctx := context.WithValue(context.Background(), oauth2.HTTPClient, idp.Client)
return idp.Config.Exchange(ctx, code)
}
@ -88,6 +131,20 @@ type GoogleUserInfo struct {
}
func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
if strings.HasPrefix(token.AccessToken, GoogleIdTokenKey) {
googleIdToken, ok := token.Extra(GoogleIdTokenKey).(GoogleIdToken)
if !ok {
return nil, errors.New("invalid googleIdToken")
}
userInfo := UserInfo{
Id: googleIdToken.Sub,
Username: googleIdToken.Email,
DisplayName: googleIdToken.Name,
Email: googleIdToken.Email,
AvatarUrl: googleIdToken.Picture,
}
return &userInfo, nil
}
url := fmt.Sprintf("https://www.googleapis.com/oauth2/v2/userinfo?alt=json&access_token=%s", token.AccessToken)
resp, err := idp.Client.Get(url)
if err != nil {

View File

@ -34,7 +34,7 @@ func StartLdapServer() {
server.Handle(routes)
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
if err != nil {
return
log.Printf("StartLdapServer() failed, ErrMsg = %s", err.Error())
}
}

View File

@ -15,8 +15,10 @@
package object
import (
"database/sql"
"fmt"
"runtime"
"strings"
"github.com/beego/beego"
"github.com/casdoor/casdoor/conf"
@ -46,6 +48,11 @@ func InitConfig() {
}
func InitAdapter() {
err := createDatabaseForPostgres(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
if err != nil {
panic(err)
}
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
@ -96,7 +103,32 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) *Adapte
return a
}
func createDatabaseForPostgres(driverName string, dataSourceName string, dbName string) error {
if driverName == "postgres" {
db, err := sql.Open(driverName, dataSourceName)
if err != nil {
return err
}
defer db.Close()
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s;", dbName))
if err != nil {
if !strings.Contains(err.Error(), "already exists") {
return err
}
}
return nil
} else {
return nil
}
}
func (a *Adapter) CreateDatabase() error {
if a.driverName == "postgres" {
return nil
}
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
if err != nil {
return err

View File

@ -161,5 +161,8 @@ func modelChangeTrigger(oldName string, newName string) error {
}
func HasRoleDefinition(m model.Model) bool {
if m == nil {
return false
}
return m["g"] != nil
}

View File

@ -26,42 +26,7 @@ import (
xormadapter "github.com/casdoor/xorm-adapter/v3"
)
func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforcer {
tableName := "permission_rule"
if len(permission.Adapter) != 0 {
adapterObj, err := getCasbinAdapter(permission.Owner, permission.Adapter)
if err != nil {
panic(err)
}
if adapterObj != nil && adapterObj.Table != "" {
tableName = adapterObj.Table
}
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
driverName := conf.GetConfigString("driverName")
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
adapter, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, tableName, tableNamePrefix, true)
if err != nil {
panic(err)
}
permissionModel, err := getModel(permission.Owner, permission.Model)
if err != nil {
panic(err)
}
m := model.Model{}
if permissionModel != nil {
m, err = GetBuiltInModel(permissionModel.ModelText)
} else {
m, err = GetBuiltInModel("")
}
if err != nil {
panic(err)
}
func getEnforcer(p *Permission, permissionIDs ...string) *casbin.Enforcer {
// Init an enforcer instance without specifying a model or adapter.
// If you specify an adapter, it will load all policies, which is a
// heavy process that can slow down the application.
@ -70,14 +35,17 @@ func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforc
panic(err)
}
err = enforcer.InitWithModelAndAdapter(m, nil)
err = p.setEnforcerModel(enforcer)
if err != nil {
panic(err)
}
enforcer.SetAdapter(adapter)
err = p.setEnforcerAdapter(enforcer)
if err != nil {
panic(err)
}
policyFilterV5 := []string{permission.GetId()}
policyFilterV5 := []string{p.GetId()}
if len(permissionIDs) != 0 {
policyFilterV5 = permissionIDs
}
@ -86,7 +54,7 @@ func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforc
V5: policyFilterV5,
}
if !HasRoleDefinition(m) {
if !HasRoleDefinition(enforcer.GetModel()) {
policyFilter.Ptype = []string{"p"}
}
@ -98,6 +66,54 @@ func getEnforcer(permission *Permission, permissionIDs ...string) *casbin.Enforc
return enforcer
}
func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
tableName := "permission_rule"
if len(p.Adapter) != 0 {
adapterObj, err := getCasbinAdapter(p.Owner, p.Adapter)
if err != nil {
return err
}
if adapterObj != nil && adapterObj.Table != "" {
tableName = adapterObj.Table
}
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
driverName := conf.GetConfigString("driverName")
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
casbinAdapter, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, tableName, tableNamePrefix, true)
if err != nil {
return err
}
enforcer.SetAdapter(casbinAdapter)
return nil
}
func (p *Permission) setEnforcerModel(enforcer *casbin.Enforcer) error {
permissionModel, err := getModel(p.Owner, p.Model)
if err != nil {
return err
}
// TODO: return error if permissionModel is nil.
m := model.Model{}
if permissionModel != nil {
m, err = GetBuiltInModel(permissionModel.ModelText)
} else {
m, err = GetBuiltInModel("")
}
if err != nil {
return err
}
err = enforcer.InitWithModelAndAdapter(m, nil)
if err != nil {
return err
}
return nil
}
func getPolicies(permission *Permission) [][]string {
var policies [][]string

View File

@ -16,6 +16,7 @@ package object
import (
"fmt"
"strings"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
@ -76,6 +77,10 @@ func GetPaginationResources(owner, user string, offset, limit int, field, value,
}
func getResource(owner string, name string) (*Resource, error) {
if !strings.HasPrefix(name, "/") {
name = "/" + name
}
resource := Resource{Owner: owner, Name: name}
existed, err := adapter.Engine.Get(&resource)
if err != nil {

View File

@ -26,6 +26,7 @@ import (
"time"
"github.com/beevik/etree"
"github.com/casdoor/casdoor/i18n"
"github.com/casdoor/casdoor/util"
dsig "github.com/russellhaering/goxmldsig"
)
@ -122,6 +123,13 @@ var stToServiceResponse sync.Map
// pgt is short for proxy granting ticket
var pgtToServiceResponse sync.Map
func CheckCasRestrict(application *Application, lang string, service string) error {
if len(application.RedirectUris) > 0 && !application.IsRedirectUriValid(service) {
return fmt.Errorf(i18n.Translate(lang, "token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), service)
}
return nil
}
func StoreCasTokenForPgt(token *CasAuthenticationSuccess, service, userId string) string {
pgt := fmt.Sprintf("PGT-%s", util.GenerateId())
pgtToServiceResponse.Store(pgt, &CasAuthenticationSuccessWrapper{

View File

@ -156,7 +156,7 @@ func AuthzFilter(ctx *context.Context) {
urlPath := getUrlPath(ctx.Request.URL.Path)
objOwner, objName := "", ""
if urlPath != "/api/get-app-login" {
if urlPath != "/api/get-app-login" && urlPath != "/api/get-resource" {
objOwner, objName = getObject(ctx)
}

View File

@ -23,7 +23,7 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
case "AWS S3":
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "MinIO":
return NewMinIOS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
return NewMinIOS3StorageProvider(clientId, clientSecret, "_", bucket, endpoint)
case "Aliyun OSS":
return NewAliyunOssStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Tencent Cloud COS":

View File

@ -33,6 +33,7 @@
"react-device-detect": "^2.2.2",
"react-dom": "^18.2.0",
"react-github-corner": "^2.5.0",
"react-google-one-tap-login": "^0.1.1",
"react-helmet": "^6.1.0",
"react-highlight-words": "^0.18.0",
"react-i18next": "^11.8.7",

View File

@ -68,7 +68,7 @@ class AdapterEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@ -80,8 +80,9 @@ class AdapterEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
models: res,
models: res.data,
});
});
}

View File

@ -119,7 +119,7 @@ class ApplicationEditPage extends React.Component {
getApplication() {
ApplicationBackend.getApplication("admin", this.state.applicationName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -129,32 +129,33 @@ class ApplicationEditPage extends React.Component {
return;
}
if (res.grantTypes === null || res.grantTypes === undefined || res.grantTypes.length === 0) {
res.grantTypes = ["authorization_code"];
const application = res.data;
if (application.grantTypes === null || application.grantTypes === undefined || application.grantTypes.length === 0) {
application.grantTypes = ["authorization_code"];
}
if (res.tags === null || res.tags === undefined) {
res.tags = [];
if (application.tags === null || application.tags === undefined) {
application.tags = [];
}
this.setState({
application: res,
application: application,
});
this.getCerts(res.organization);
this.getCerts(application.organization);
});
}
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
if (res?.status === "error") {
if (res.status === "error") {
this.setState({
isAuthorized: false,
});
} else {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
}
});
@ -164,7 +165,7 @@ class ApplicationEditPage extends React.Component {
CertBackend.getCerts(owner)
.then((res) => {
this.setState({
certs: (res.msg === undefined) ? res : [],
certs: res.data || [],
});
});
}
@ -184,9 +185,9 @@ class ApplicationEditPage extends React.Component {
getSamlMetadata() {
ApplicationBackend.getSamlMetadata("admin", this.state.applicationName)
.then((res) => {
.then((data) => {
this.setState({
samlMetadata: res,
samlMetadata: data,
});
});
}

View File

@ -45,7 +45,7 @@ class CertEditPage extends React.Component {
getCert() {
CertBackend.getCert(this.state.owner, this.state.certName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -56,7 +56,7 @@ class CertEditPage extends React.Component {
}
this.setState({
cert: res,
cert: res.data,
});
});
}
@ -65,7 +65,7 @@ class CertEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@ -41,7 +41,7 @@ class ChatEditPage extends React.Component {
getChat() {
ChatBackend.getChat("admin", this.state.chatName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -51,10 +51,10 @@ class ChatEditPage extends React.Component {
return;
}
this.setState({
chat: res,
chat: res.data,
});
this.getUsers(res.organization);
this.getUsers(res.data.organization);
});
}
@ -62,7 +62,7 @@ class ChatEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@ -76,7 +76,7 @@ class ChatEditPage extends React.Component {
}
this.setState({
users: res,
users: res.data,
});
});
}

View File

@ -79,7 +79,8 @@ class ChatPage extends BaseListPage {
getMessages(chatName) {
MessageBackend.getChatMessages(chatName)
.then((messages) => {
.then((res) => {
const messages = res.data;
this.setState({
messages: messages,
});
@ -229,7 +230,7 @@ class ChatPage extends BaseListPage {
</div>
)
}
<ChatBox messages={this.state.messages} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
<ChatBox messages={this.state.messages || []} sendMessage={(text) => {this.sendMessage(text);}} account={this.props.account} />
</div>
</div>
);

View File

@ -79,7 +79,9 @@ class EntryPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
const themeData = res !== null ? Setting.getThemeData(res.organizationObj, res) : Conf.ThemeDefault;
const application = res.data;
const themeData = application !== null ? Setting.getThemeData(application.organizationObj, application) : Conf.ThemeDefault;
this.props.updataThemeData(themeData);
});
};

View File

@ -67,7 +67,7 @@ class GroupEditPage extends React.Component {
.then((res) => {
if (res.status === "ok") {
this.setState({
organizations: res.data,
organizations: res.data || [],
});
}
});

View File

@ -55,7 +55,7 @@ class LdapEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@ -46,7 +46,7 @@ class MessageEditPage extends React.Component {
getMessage() {
MessageBackend.getMessage("admin", this.state.messageName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -54,11 +54,11 @@ class MessageEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
message: res,
});
this.getUsers(res.organization);
this.setState({
message: res.data,
});
this.getUsers(res.data.organization);
});
}
@ -66,7 +66,7 @@ class MessageEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@ -75,7 +75,7 @@ class MessageEditPage extends React.Component {
ChatBackend.getChats("admin")
.then((res) => {
this.setState({
chats: (res.msg === undefined) ? res : [],
chats: res.data || [],
});
});
}
@ -87,8 +87,9 @@ class MessageEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}

View File

@ -48,7 +48,7 @@ class ModelEditPage extends React.Component {
getModel() {
ModelBackend.getModel(this.state.organizationName, this.state.modelName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -59,7 +59,7 @@ class ModelEditPage extends React.Component {
}
this.setState({
model: res,
model: res.data,
});
});
}
@ -68,7 +68,7 @@ class ModelEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@ -75,7 +75,7 @@ class OrganizationEditPage extends React.Component {
}
this.setState({
applications: res,
applications: res.data || [],
});
});
}

View File

@ -41,14 +41,14 @@ class PaymentEditPage extends React.Component {
getPayment() {
PaymentBackend.getPayment("admin", this.state.paymentName)
.then((payment) => {
if (payment === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
payment: payment,
payment: res.data,
});
Setting.scrollToDiv("invoice-area");

View File

@ -41,12 +41,12 @@ class PaymentResultPage extends React.Component {
getPayment() {
PaymentBackend.getPayment("admin", this.state.paymentName)
.then((payment) => {
.then((res) => {
this.setState({
payment: payment,
payment: res.data,
});
if (payment.state === "Created") {
if (res.data.state === "Created") {
this.setState({timeout: setTimeout(() => this.getPayment(), 1000)});
}
});

View File

@ -50,7 +50,9 @@ class PermissionEditPage extends React.Component {
getPermission() {
PermissionBackend.getPermission(this.state.organizationName, this.state.permissionName)
.then((res) => {
if (res === null) {
const permission = res.data;
if (permission === null) {
this.props.history.push("/404");
return;
}
@ -61,14 +63,14 @@ class PermissionEditPage extends React.Component {
}
this.setState({
permission: res,
permission: permission,
});
this.getUsers(res.owner);
this.getRoles(res.owner);
this.getModels(res.owner);
this.getResources(res.owner);
this.getModel(res.owner, res.model);
this.getUsers(permission.owner);
this.getRoles(permission.owner);
this.getModels(permission.owner);
this.getResources(permission.owner);
this.getModel(permission.owner, permission.model);
});
}
@ -76,7 +78,7 @@ class PermissionEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@ -88,8 +90,9 @@ class PermissionEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}
@ -101,8 +104,9 @@ class PermissionEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
roles: res,
roles: res.data,
});
});
}
@ -114,21 +118,21 @@ class PermissionEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
models: res,
models: res.data,
});
});
}
getModel(organizationName, modelName) {
if (modelName === "") {
return;
}
ModelBackend.getModel(organizationName, modelName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
model: res,
model: res.data,
});
});
}
@ -137,7 +141,7 @@ class PermissionEditPage extends React.Component {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
resources: (res.msg === undefined) ? res : [],
resources: res.data || [],
});
});
}

View File

@ -46,18 +46,18 @@ class PlanEditPage extends React.Component {
getPlan() {
PlanBackend.getPlan(this.state.organizationName, this.state.planName)
.then((plan) => {
if (plan === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
plan: plan,
plan: res.data,
});
this.getUsers(plan.owner);
this.getRoles(plan.owner);
this.getUsers(this.state.organizationName);
this.getRoles(this.state.organizationName);
});
}
@ -68,8 +68,9 @@ class PlanEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
roles: res,
roles: res.data,
});
});
}
@ -81,8 +82,9 @@ class PlanEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}
@ -91,7 +93,7 @@ class PlanEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@ -44,13 +44,12 @@ class PricingEditPage extends React.Component {
this.getPricing();
this.getOrganizations();
this.getApplicationsByOrganization(this.state.organizationName);
this.getUserApplication();
}
getPricing() {
PricingBackend.getPricing(this.state.organizationName, this.state.pricingName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -61,9 +60,9 @@ class PricingEditPage extends React.Component {
}
this.setState({
pricing: res,
pricing: res.data,
});
this.getPlans(res.owner);
this.getPlans(this.state.organizationName);
});
}
@ -74,8 +73,9 @@ class PricingEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
plans: res,
plans: res.data,
});
});
}
@ -84,7 +84,16 @@ class PricingEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
getApplicationsByOrganization(organizationName) {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
applications: res.data || [],
});
});
}
@ -107,28 +116,6 @@ class PricingEditPage extends React.Component {
});
}
getApplicationsByOrganization(organizationName) {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
applications: (res.msg === undefined) ? res : [],
});
});
}
getUserApplication() {
ApplicationBackend.getUserApplication(this.state.organizationName, this.state.userName)
.then((res) => {
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
application: res,
});
});
}
renderPricing() {
return (
<Card size="small" title={

View File

@ -48,7 +48,7 @@ class ProductBuyPage extends React.Component {
}
this.setState({
product: res,
product: res.data,
});
});
}

View File

@ -46,14 +46,14 @@ class ProductEditPage extends React.Component {
getProduct() {
ProductBackend.getProduct(this.state.organizationName, this.state.productName)
.then((product) => {
if (product === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
product: product,
product: res.data,
});
});
}
@ -62,7 +62,7 @@ class ProductEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@ -50,7 +50,7 @@ class ProviderEditPage extends React.Component {
getProvider() {
ProviderBackend.getProvider(this.state.owner, this.state.providerName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -72,7 +72,7 @@ class ProviderEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: res.msg === undefined ? res : [],
organizations: res.data || [],
});
});
}
@ -527,7 +527,7 @@ class ProviderEditPage extends React.Component {
)
}
{
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") || this.state.provider.category === "Web3" ? null : (
(this.state.provider.category === "Captcha" && this.state.provider.type === "Default") || (this.state.provider.category === "Web3") || (this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ? null : (
<React.Fragment>
{
this.state.provider.category === "AI" ? null : (
@ -616,36 +616,42 @@ class ProviderEditPage extends React.Component {
}
{this.state.provider.category === "Storage" ? (
<div>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.endpoint} onChange={e => {
this.updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.intranetEndpoint} onChange={e => {
this.updateProviderField("intranetEndpoint", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.bucket} onChange={e => {
this.updateProviderField("bucket", e.target.value);
}} />
</Col>
</Row>
{["Local File System"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint"), i18next.t("provider:Region endpoint for Internet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.endpoint} onChange={e => {
this.updateProviderField("endpoint", e.target.value);
}} />
</Col>
</Row>
)}
{["Local File System", "MinIO", "Tencent Cloud COS"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Endpoint (Intranet)"), i18next.t("provider:Region endpoint for Intranet"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.intranetEndpoint} onChange={e => {
this.updateProviderField("intranetEndpoint", e.target.value);
}} />
</Col>
</Row>
)}
{["Local File System"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Bucket"), i18next.t("provider:Bucket - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.bucket} onChange={e => {
this.updateProviderField("bucket", e.target.value);
}} />
</Col>
</Row>
)}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Path prefix"), i18next.t("provider:Path prefix - Tooltip"))} :
@ -656,17 +662,19 @@ class ProviderEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.domain} onChange={e => {
this.updateProviderField("domain", e.target.value);
}} />
</Col>
</Row>
{["AWS S3", "MinIO", "Tencent Cloud COS"].includes(this.state.provider.type) ? (
{["MinIO"].includes(this.state.provider.type) ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.provider.domain} disabled={this.state.provider.type === "Local File System"} onChange={e => {
this.updateProviderField("domain", e.target.value);
}} />
</Col>
</Row>
)}
{["AWS S3", "Tencent Cloud COS"].includes(this.state.provider.type) ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={2}>
{Setting.getLabel(i18next.t("provider:Region ID"), i18next.t("provider:Region ID - Tooltip"))} :

View File

@ -43,7 +43,7 @@ class RoleEditPage extends React.Component {
getRole() {
RoleBackend.getRole(this.state.organizationName, this.state.roleName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -53,11 +53,11 @@ class RoleEditPage extends React.Component {
}
this.setState({
role: res,
role: res.data,
});
this.getUsers(res.owner);
this.getRoles(res.owner);
this.getUsers(this.state.organizationName);
this.getRoles(this.state.organizationName);
});
}
@ -65,7 +65,7 @@ class RoleEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@ -77,8 +77,9 @@ class RoleEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}
@ -90,8 +91,9 @@ class RoleEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
roles: res,
roles: res.data,
});
});
}

View File

@ -47,7 +47,7 @@ class SubscriptionEditPage extends React.Component {
getSubscription() {
SubscriptionBackend.getSubscription(this.state.organizationName, this.state.subscriptionName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -58,11 +58,11 @@ class SubscriptionEditPage extends React.Component {
}
this.setState({
subscription: res,
subscription: res.data,
});
this.getUsers(res.owner);
this.getPlanes(res.owner);
this.getUsers(this.state.organizationName);
this.getPlanes(this.state.organizationName);
});
}
@ -70,7 +70,7 @@ class SubscriptionEditPage extends React.Component {
PlanBackend.getPlans(organizationName)
.then((res) => {
this.setState({
planes: res,
planes: res.data,
});
});
}
@ -82,8 +82,9 @@ class SubscriptionEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
users: res,
users: res.data,
});
});
}
@ -92,7 +93,7 @@ class SubscriptionEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@ -48,7 +48,7 @@ class SyncerEditPage extends React.Component {
getSyncer() {
SyncerBackend.getSyncer("admin", this.state.syncerName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -59,7 +59,7 @@ class SyncerEditPage extends React.Component {
}
this.setState({
syncer: res,
syncer: res.data,
});
});
}
@ -68,7 +68,7 @@ class SyncerEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@ -36,7 +36,7 @@ class TokenEditPage extends React.Component {
getToken() {
TokenBackend.getToken("admin", this.state.tokenName)
.then((res) => {
if (res === null) {
if (res.data === null) {
this.props.history.push("/404");
return;
}
@ -47,7 +47,7 @@ class TokenEditPage extends React.Component {
}
this.setState({
token: res,
token: res.data,
});
});
}

View File

@ -75,19 +75,20 @@ class UserEditPage extends React.Component {
getUser() {
UserBackend.getUser(this.state.organizationName, this.state.userName)
.then((data) => {
if (data === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
if (data.status === null || data.status !== "error") {
this.setState({
user: data,
multiFactorAuths: data?.multiFactorAuths ?? [],
});
if (res.status === "error") {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
user: res.data,
multiFactorAuths: res.data?.multiFactorAuths ?? [],
loading: false,
});
});
@ -108,7 +109,7 @@ class UserEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}
@ -117,7 +118,7 @@ class UserEditPage extends React.Component {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
applications: (res.msg === undefined) ? res : [],
applications: res.data || [],
});
});
}
@ -129,12 +130,10 @@ class UserEditPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.setState({
application: res,
});
this.setState({
isGroupsVisible: res.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
application: res.data,
isGroupsVisible: res.data?.organizationObj.accountItems?.some((item) => item.name === "Groups" && item.visible),
});
});
}

View File

@ -122,14 +122,14 @@ class WebhookEditPage extends React.Component {
getWebhook() {
WebhookBackend.getWebhook("admin", this.state.webhookName)
.then((webhook) => {
if (webhook === null) {
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
webhook: webhook,
webhook: res.data,
});
});
}
@ -138,7 +138,7 @@ class WebhookEditPage extends React.Component {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: (res.msg === undefined) ? res : [],
organizations: res.data || [],
});
});
}

View File

@ -68,7 +68,7 @@ class ForgetPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
});
}
getApplicationObj() {

View File

@ -14,6 +14,9 @@
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
import {useGoogleOneTapLogin} from "react-google-one-tap-login";
import * as Setting from "../Setting";
import * as Provider from "./Provider";
function Icon({width = 24, height = 24, color}) {
return <img src={`${StaticBaseUrl}/buttons/google.svg`} alt="Sign in with Google" />;
@ -29,4 +32,29 @@ const config = {
const GoogleLoginButton = createButton(config);
export function GoogleOneTapLoginVirtualButton(prop) {
const application = prop.application;
const providerConf = prop.providerConf;
// https://stackoverflow.com/questions/62281579/google-one-tap-sign-in-ui-not-displayed-after-clicking-the-close-button
// document.cookie = "g_state=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT";
useGoogleOneTapLogin({
googleAccountConfigs: {
client_id: providerConf.provider.clientId,
},
onError: (error) => {
Setting.showMessage("error", error);
},
onSuccess: (response) => {
const code = "GoogleIdToken-" + JSON.stringify(response);
const authUrlParams = new URLSearchParams(Provider.getAuthUrl(application, providerConf.provider, "signup"));
const state = authUrlParams.get("state");
let redirectUri = authUrlParams.get("redirect_uri");
redirectUri = `${redirectUri}?state=${state}&code=${encodeURIComponent(code)}`;
Setting.goToLink(redirectUri);
},
disableCancelOnUnmount: true,
});
}
export default GoogleLoginButton;

View File

@ -36,7 +36,7 @@ import {CaptchaModal} from "../common/modal/CaptchaModal";
import {CaptchaRule} from "../common/modal/CaptchaModal";
import RedirectForm from "../common/RedirectForm";
import {MfaAuthVerifyForm, NextMfa, RequiredMfa} from "./mfa/MfaAuthVerifyForm";
import {GoogleOneTapLoginVirtualButton} from "./GoogleLoginButton";
class LoginPage extends React.Component {
constructor(props) {
super(props);
@ -170,10 +170,15 @@ class LoginPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
});
} else {
OrganizationBackend.getDefaultApplication("admin", this.state.owner)
let redirectUri = "";
if (this.state.type === "cas") {
const casParams = Util.getCasParameters();
redirectUri = casParams.service;
}
OrganizationBackend.getDefaultApplication("admin", this.state.owner, this.state.type, redirectUri)
.then((res) => {
if (res.status === "ok") {
const application = res.data;
@ -183,9 +188,9 @@ class LoginPage extends React.Component {
});
} else {
this.onUpdateApplication(null);
Setting.showMessage("error", res.msg);
this.props.history.push("/404");
this.setState({
msg: res.msg,
});
}
});
}
@ -418,6 +423,16 @@ class LoginPage extends React.Component {
}
}
renderOtherFormProvider(application) {
for (const providerConf of application.providers) {
if (providerConf.provider?.type === "Google" && providerConf.rule === "OneTap" && this.props.preview !== "auto") {
return (
<GoogleOneTapLoginVirtualButton application={application} providerConf={providerConf} />
);
}
}
}
renderForm(application) {
if (this.state.msg !== null) {
return Util.renderMessage(this.state.msg);
@ -575,6 +590,9 @@ class LoginPage extends React.Component {
return ProviderButton.renderProviderLogo(providerItem.provider, application, 30, 5, "small", this.props.location);
})
}
{
this.renderOtherFormProvider(application)
}
</Form.Item>
</Form>
);
@ -596,6 +614,9 @@ class LoginPage extends React.Component {
return ProviderButton.renderProviderLogo(providerItem.provider, application, 40, 10, "big", this.props.location);
})
}
{
this.renderOtherFormProvider(application)
}
<div>
<br />
{

View File

@ -69,7 +69,7 @@ class MfaSetupPage extends React.Component {
return;
}
this.setState({
application: res,
application: res.data,
});
} else {
Setting.showMessage("error", i18next.t("mfa:Failed to get application"));

View File

@ -63,7 +63,7 @@ class PromptPage extends React.Component {
}
this.setState({
user: res,
user: res.data,
});
});
}
@ -80,9 +80,9 @@ class PromptPage extends React.Component {
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
this.setState({
application: res,
application: res.data,
});
});
}

View File

@ -48,9 +48,10 @@ class ResultPage extends React.Component {
Setting.showMessage("error", res.msg);
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
this.setState({
application: res,
application: res.data,
});
});
}

View File

@ -114,7 +114,7 @@ class SignupPage extends React.Component {
return;
}
this.onUpdateApplication(res);
this.onUpdateApplication(res.data);
});
}

View File

@ -70,8 +70,8 @@ export function deleteOrganization(organization) {
}).then(res => res.json());
}
export function getDefaultApplication(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-default-application?id=${owner}/${encodeURIComponent(name)}`, {
export function getDefaultApplication(owner, name, type = "", redirectUri = "") {
return fetch(`${Setting.ServerUrl}/api/get-default-application?id=${owner}/${encodeURIComponent(name)}&type=${type}&redirectUri=${redirectUri}`, {
method: "GET",
credentials: "include",
headers: {

View File

@ -36,7 +36,7 @@ class HomePage extends React.Component {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
applications: (res.msg === undefined) ? res : [],
applications: res.data || [],
});
});
}

View File

@ -15,6 +15,7 @@
import {Checkbox, Form, Modal} from "antd";
import i18next from "i18next";
import React, {useEffect, useState} from "react";
import * as Setting from "../../Setting";
export const AgreementModal = (props) => {
const {open, onOk, onCancel, application} = props;
@ -31,14 +32,16 @@ export const AgreementModal = (props) => {
<Modal
title={i18next.t("signup:Terms of Use")}
open={open}
width={"55vw"}
width={Setting.isMobile() ? "100vw" : "55vw"}
closable={false}
okText={i18next.t("signup:Accept")}
cancelText={i18next.t("signup:Decline")}
onOk={onOk}
onCancel={onCancel}
style={{top: Setting.isMobile() ? "5px" : ""}}
maskStyle={{backgroundColor: Setting.isMobile() ? "white" : ""}}
>
<iframe title={"terms"} style={{border: 0, width: "100%", height: "60vh"}} srcDoc={doc} />
<iframe title={"terms"} style={{border: 0, width: "100%", height: Setting.isMobile() ? "80vh" : "60vh"}} srcDoc={doc} />
</Modal>
);
};

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "Erstellte Zeit",
"Custom": "Custom",
"Default": "Default",
"Default application": "Standard Anwendung",
"Default application - Tooltip": "Standard-Anwendung für Benutzer, die direkt von der Organisationsseite registriert wurden",
"Default avatar": "Standard-Avatar",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "Vom Server verwendet, um die API des Verifizierungscodes-Providers für die Verifizierung aufzurufen",
"Send Testing Email": "Senden Sie eine Test-E-Mail",
"Send Testing SMS": "Sende Test-SMS",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "Signatur Namen",
"Sign Name - Tooltip": "Name der Signatur, die verwendet werden soll",
"Sign request": "Signaturanfrage",

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "Created time",
"Custom": "Custom",
"Default": "Default",
"Default application": "Default application",
"Default application - Tooltip": "Default application for users registered directly from the organization page",
"Default avatar": "Default avatar",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "Used by the server to call the verification code provider API for verification",
"Send Testing Email": "Send Testing Email",
"Send Testing SMS": "Send Testing SMS",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "Sign Name",
"Sign Name - Tooltip": "Name of the signature to be used",
"Sign request": "Sign request",

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "Tiempo creado",
"Custom": "Custom",
"Default": "Default",
"Default application": "Aplicación predeterminada",
"Default application - Tooltip": "Aplicación predeterminada para usuarios registrados directamente desde la página de la organización",
"Default avatar": "Avatar predeterminado",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "Utilizado por el servidor para llamar a la API del proveedor de códigos de verificación para verificar",
"Send Testing Email": "Enviar correo electrónico de prueba",
"Send Testing SMS": "Enviar SMS de prueba",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "Firma de Nombre",
"Sign Name - Tooltip": "Nombre de la firma a ser utilizada",
"Sign request": "Solicitud de firma",

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "Temps créé",
"Custom": "Custom",
"Default": "Default",
"Default application": "Application par défaut",
"Default application - Tooltip": "Application par défaut pour les utilisateurs enregistrés directement depuis la page de l'organisation",
"Default avatar": "Avatar par défaut",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "Utilisé par le serveur pour appeler l'API du fournisseur de code de vérification pour vérifier",
"Send Testing Email": "Envoyer un e-mail de test",
"Send Testing SMS": "Envoyer des messages SMS de tests",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "Nom de signature",
"Sign Name - Tooltip": "Nom de la signature à utiliser",
"Sign request": "Demande de signature",

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "Waktu dibuat",
"Custom": "Custom",
"Default": "Default",
"Default application": "Aplikasi default",
"Default application - Tooltip": "Aplikasi default untuk pengguna yang terdaftar langsung dari halaman organisasi",
"Default avatar": "Avatar default",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "Digunakan oleh server untuk memanggil API penyedia kode verifikasi untuk melakukan verifikasi",
"Send Testing Email": "Kirim Email Uji Coba",
"Send Testing SMS": "Kirim SMS Uji Coba",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "Tanda Tangan",
"Sign Name - Tooltip": "Nama tanda tangan yang akan digunakan",
"Sign request": "Permintaan tanda tangan",

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "作成された時間",
"Custom": "Custom",
"Default": "Default",
"Default application": "デフォルトアプリケーション",
"Default application - Tooltip": "組織ページから直接登録されたユーザーのデフォルトアプリケーション",
"Default avatar": "デフォルトのアバター",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "認証のためにサーバーによって使用され、認証コードプロバイダAPIを呼び出すためのもの",
"Send Testing Email": "テスト用メールを送信する",
"Send Testing SMS": "テストSMSを送信してください",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "署名",
"Sign Name - Tooltip": "使用する署名の名前",
"Sign request": "サインリクエスト",

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "작성한 시간",
"Custom": "Custom",
"Default": "Default",
"Default application": "기본 애플리케이션",
"Default application - Tooltip": "조직 페이지에서 직접 등록한 사용자의 기본 응용 프로그램",
"Default avatar": "기본 아바타",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "검증을 위해 서버에서 인증 코드 공급자 API를 호출하는 데 사용됩니다",
"Send Testing Email": "테스트 이메일을 보내기",
"Send Testing SMS": "테스트 SMS를 보내세요",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "신명서",
"Sign Name - Tooltip": "사용할 서명의 이름",
"Sign request": "표지 요청",

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "Hora de Criação",
"Custom": "Custom",
"Default": "Default",
"Default application": "Aplicação padrão",
"Default application - Tooltip": "Aplicação padrão para usuários registrados diretamente na página da organização",
"Default avatar": "Avatar padrão",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "Usada pelo servidor para chamar a API do fornecedor de código de verificação para verificação",
"Send Testing Email": "Enviar E-mail de Teste",
"Send Testing SMS": "Enviar SMS de Teste",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "Nome do Sinal",
"Sign Name - Tooltip": "Nome da assinatura a ser usada",
"Sign request": "Solicitação de assinatura",

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "Созданное время",
"Custom": "Custom",
"Default": "Default",
"Default application": "Приложение по умолчанию",
"Default application - Tooltip": "По умолчанию приложение для пользователей, зарегистрированных непосредственно со страницы организации",
"Default avatar": "Стандартный аватар",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "Используется сервером для вызова API-интерфейса поставщика кода подтверждения для проверки",
"Send Testing Email": "Отправить тестовое письмо",
"Send Testing SMS": "Отправить тестовое SMS-сообщение",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "Подпись имени",
"Sign Name - Tooltip": "Имя подписи, которую нужно использовать",
"Sign request": "Подписать запрос",

View File

@ -197,6 +197,7 @@
"Confirm": "Confirm",
"Created time": "Thời gian tạo",
"Custom": "Custom",
"Default": "Default",
"Default application": "Ứng dụng mặc định",
"Default application - Tooltip": "Ứng dụng mặc định cho người dùng đăng ký trực tiếp từ trang tổ chức",
"Default avatar": "Hình đại diện mặc định",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "Được sử dụng bởi máy chủ để gọi API nhà cung cấp mã xác minh để xác minh",
"Send Testing Email": "Gửi Email kiểm tra",
"Send Testing SMS": "Gửi SMS kiểm tra",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "Ký tên",
"Sign Name - Tooltip": "Tên chữ ký sẽ được sử dụng",
"Sign request": "Yêu cầu ký tên",

View File

@ -197,6 +197,7 @@
"Confirm": "确认",
"Created time": "创建时间",
"Custom": "自定义",
"Default": "默认",
"Default application": "默认应用",
"Default application - Tooltip": "直接从组织页面注册的用户默认所属的应用",
"Default avatar": "默认头像",
@ -722,6 +723,8 @@
"Secret key - Tooltip": "用于服务端调用验证码提供商API进行验证",
"Send Testing Email": "发送测试邮件",
"Send Testing SMS": "发送测试短信",
"Sender number": "Sender number",
"Sender number - Tooltip": "Sender number - Tooltip",
"Sign Name": "签名名称",
"Sign Name - Tooltip": "签名名称",
"Sign request": "签名请求",

View File

@ -178,21 +178,37 @@ class ProviderTable extends React.Component {
key: "rule",
width: "100px",
render: (text, record, index) => {
if (record.provider?.category !== "Captcha") {
if (record.provider?.type === "Google") {
if (text === "None") {
text = "Default";
}
return (
<Select virtual={false} style={{width: "100%"}}
value={text}
defaultValue="Default"
onChange={value => {
this.updateField(table, index, "rule", value);
}} >
<Option key="Default" value="Default">{i18next.t("general:Default")}</Option>
<Option key="OneTap" value="OneTap">{"One Tap"}</Option>
</Select>
);
} else if (record.provider?.category === "Captcha") {
return (
<Select virtual={false} style={{width: "100%"}}
value={text}
defaultValue="None"
onChange={value => {
this.updateField(table, index, "rule", value);
}} >
<Option key="None" value="None">{i18next.t("general:None")}</Option>
<Option key="Dynamic" value="Dynamic">{i18next.t("application:Dynamic")}</Option>
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
</Select>
);
} else {
return null;
}
return (
<Select virtual={false} style={{width: "100%"}}
value={text}
defaultValue="None"
onChange={value => {
this.updateField(table, index, "rule", value);
}} >
<Option key="None" value="None">{i18next.t("general:None")}</Option>
<Option key="Dynamic" value="Dynamic">{i18next.t("application:Dynamic")}</Option>
<Option key="Always" value="Always">{i18next.t("application:Always")}</Option>
</Select>
);
},
},
{

View File

@ -10199,6 +10199,11 @@ react-github-corner@^2.5.0:
resolved "https://registry.yarnpkg.com/react-github-corner/-/react-github-corner-2.5.0.tgz#e350d0c69f69c075bc0f1d2a6f1df6ee91da31f2"
integrity sha512-ofds9l6n61LJc6ML+jSE6W9ZSQvATcMR9evnHPXua1oDYj289HKODnVqFUB/g2a4ieBjDHw416iHP3MjqnU76Q==
react-google-one-tap-login@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/react-google-one-tap-login/-/react-google-one-tap-login-0.1.1.tgz#12a61e63e19251622cf1575b601d79fd4d07847a"
integrity sha512-RCbOfR3Z7VcRae4AzrLoBfY/aqqdN24pkQeNz9CJ0W65C9SY3vAwF0Yp8mwpeFwsugaZ5b1HZsfhUYWtb3IMrw==
react-helmet@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.1.0.tgz#a750d5165cb13cf213e44747502652e794468726"