Compare commits

...

55 Commits

Author SHA1 Message Date
984cca3f97 Revert "feat: more RFC like LDAP server behaviour" 2024-01-15 13:54:30 +08:00
1161310f81 feat: improve README.md 2024-01-15 10:14:01 +08:00
48ba5f91ed feat: add Synology NAS storage provider (#2605) 2024-01-14 22:38:31 +08:00
53df2c2704 fix: add semantic versioning for helm charts (#2603) 2024-01-14 09:44:16 +08:00
78066da208 Improve setCorsHeaders() for "include" mode 2024-01-13 23:46:05 +08:00
60096468fe fix: fix CI email 2024-01-13 18:12:52 +08:00
39d6bc10f7 Fix GetCaptchaStatus() crash if not logged in 2024-01-13 18:04:38 +08:00
177f2f2f11 Add userId param to GetAllObjects() API 2024-01-13 18:03:40 +08:00
79b393afee feat: add regex to SignupTable 2024-01-13 16:08:49 +08:00
5bb12a30d4 Don't show two errors in verificationCode login page 2024-01-13 16:01:22 +08:00
fdb68bf9c8 Rename to SigninMethodTable 2024-01-13 15:53:01 +08:00
37748850c8 Fix nameFormat in SamlItem 2024-01-13 15:32:49 +08:00
8968396ae5 Fix bug in getDefaultLoginMethod() 2024-01-13 12:13:09 +08:00
f5395f15f9 feat: fix isSigninMethodEnabled() bug in frontend 2024-01-13 11:35:06 +08:00
73e44df867 Improve GetAllRoles() error handling 2024-01-13 10:06:08 +08:00
0b575ccf84 Refactor getAllValues() 2024-01-13 09:58:55 +08:00
9b7f465a47 Fix failedSigninFrozenTime typo 2024-01-13 02:12:29 +08:00
b1fe28fb83 Refactor application.FailedSigninLimit code 2024-01-13 02:09:18 +08:00
530d054adb feat: ci should commit index.yaml and push to docker hub (#2600) 2024-01-11 16:10:08 +08:00
a2b9f9baaf feat: support "JWT-Custom" to customize user properties inside access token (#2594)
* feat: add custom attribute to access token

* Update token_jwt.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2024-01-10 00:59:02 +08:00
a2d20fcb63 Update i18n 2024-01-09 22:16:17 +08:00
b118a3bb76 Add TokenFields to application 2024-01-09 22:09:21 +08:00
280867d0cb Add checkSigninErrorTimes() for LDAP signin 2024-01-09 21:53:44 +08:00
30fa2f7d81 Disable LDAP login method by default 2024-01-09 21:36:09 +08:00
518288691d fix(ci): fix the helm publish step (#2593)
fixes https://github.com/casdoor/casdoor-helm/issues/3
2024-01-09 17:48:01 +08:00
ffa54247cd feat: add LDAP signin method (#2591)
Add support for LDAP login methods
Add option to control LDAP user in password login method.
2024-01-08 21:07:34 +08:00
0199ad9aaa fix: missing table prefix in get user group (#2590)
- Sort field and order field are missing table name prefix

Co-authored-by: xgenvn <brian7.ng@gmail.com>
2024-01-08 21:07:13 +08:00
b9d171718f chore(helm): move to dedicated helm-repo (#2587)
* chore(helm): move to dedicated helm-repo: https://github.com/casdoor/casdoor-helm

* feat(actions): explicit checkout helm repo

* chore: feedback from pr comment
2024-01-08 02:02:05 +08:00
e841d0ba8e feat: fix /api/send-email API for app user 2024-01-07 21:11:22 +08:00
e5a9594f90 Hide Google OneTap in iframe 2024-01-07 10:33:25 +08:00
c542929835 fix: add vscode local debugging support (#2585) 2024-01-07 09:26:33 +08:00
86dea71efd ci: update helm index.yaml 2024-01-06 19:31:07 +00:00
9e536850fd feat(helm): support for extra volume mounts (#2584)
* feat(helm): support for extraVolumes and extraVolumeMounts

* ci(helm): run helm unittests
2024-01-07 03:30:44 +08:00
fddd4a12b8 chore: update helm version to v1.492.0 (#2582) 2024-01-07 00:14:53 +08:00
2d6fae32be feat: support custom config path via "config" 2024-01-06 14:09:48 +08:00
741cff99df Remove isCreateDatabaseDefined 2024-01-06 14:08:34 +08:00
cad9c28e92 feat: helm hpa yaml must reference correct apiVersion (#2581) 2024-01-06 08:55:59 +08:00
524cf4dda5 feat: fix update application failed for permissions with the same name (#2579)
* fixed: update application failed where have two same permission in different organization

* Update application.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2024-01-05 20:45:55 +08:00
077a1cb8b7 fix: support owner parameter in enforce API (#2578) 2024-01-05 15:12:59 +08:00
00efdf1d03 Fix EmailVerified in UserInfo() 2024-01-05 09:37:42 +08:00
aa543f1abb feat: more RFC like LDAP server behaviour (#2574)
* feat: more RFC like LDAP server behaviour

* Extend FieldRelationMap to support case insensitive mapping, add more fields definition

* feat: Add group syncing for LDAP server
2024-01-05 09:24:12 +08:00
1d1d3049bd feat: fix dropped getAffiliationMap error in object (#2576) 2024-01-05 09:03:39 +08:00
4f497d44a5 Enable at least password login in extendApplicationWithSigninMethods() 2024-01-03 22:19:43 +08:00
369de36987 feat: add users with correct application (#2570) 2024-01-02 23:49:04 +08:00
e3f28e8b4c feat: Support more flexible login method control (#2566) 2024-01-02 21:11:52 +08:00
3373174c65 fix: add missing tableNamePrefix in some places 2023-12-31 22:40:41 +08:00
2fb79e4092 Add invitation pages 2023-12-31 21:38:36 +08:00
5846e337c7 feat: fix gofmt issue 2023-12-30 19:47:04 +08:00
44f4de1440 feat: support empty fileUrl in GetUploadFileUrl() 2023-12-30 19:06:35 +08:00
27adeb4620 Refactor initAPI() 2023-12-30 14:28:45 +08:00
5c107db43b fix: fix i18n typo 2023-12-30 00:49:39 +08:00
27187b3a54 feat: add "Reset to Default HTML" button 2023-12-30 00:47:10 +08:00
14fcedcc5d feat: support HTML in Email content 2023-12-29 23:31:50 +08:00
e7c015f288 feat: fix comment and configs for successfully generating OpenAPI typescript-axios sdk (#2560)
* fix: fix swagger.json, successfully generate java sdk

* fix:fix comment and change some content for successfully generating typescript-axios sdk
2023-12-29 15:12:40 +08:00
c4819602ec fix: add mfa API to isAllowedInDemoMode() 2023-12-26 20:06:27 +08:00
124 changed files with 3422 additions and 1299 deletions

View File

@ -1,6 +1,6 @@
name: Build
on: [push, pull_request]
on: [ push, pull_request ]
jobs:
@ -167,10 +167,8 @@ jobs:
elif [ ${old_array[1]} != ${new_array[1]} ]
then
echo ::set-output name=push::'true'
else
echo ::set-output name=push::'false'
fi
- name: Set up QEMU
@ -208,3 +206,33 @@ jobs:
platforms: linux/amd64
push: true
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
- uses: actions/checkout@v3
if: steps.should_push.outputs.push=='true'
with:
repository: casdoor/casdoor-helm
ref: 'master'
token: ${{ secrets.GH_BOT_TOKEN }}
- name: Update Helm Chart
if: steps.should_push.outputs.push=='true'
run: |
# Set the appVersion and version of the chart to the current tag
sed -i "s/appVersion: .*/appVersion: ${{steps.get-current-tag.outputs.tag }}/g" ./charts/casdoor/Chart.yaml
sed -i "s/version: .*/version: ${{steps.get-current-tag.outputs.tag }}/g" ./charts/casdoor/Chart.yaml
REGISTRY=oci://registry-1.docker.io/casbin
cd charts/casdoor
helm package .
PKG_NAME=$(ls *.tgz)
helm repo index . --url $REGISTRY --merge index.yaml
helm push $PKG_NAME $REGISTRY
rm $PKG_NAME
# Commit and push the changes back to the repository
git config --global user.name "casbin-bot"
git config --global user.email "bot@casbin.org"
git add Chart.yaml index.yaml
git commit -m "chore(helm): bump helm charts appVersion to ${{steps.get-current-tag.outputs.tag }}"
git tag ${{steps.get-current-tag.outputs.tag }}
git push origin HEAD:master --follow-tags

View File

@ -1,40 +0,0 @@
name: Helm Release
on:
push:
branches:
- master
paths:
- 'manifests/casdoor/Chart.yaml'
jobs:
release-helm-chart:
name: Release Helm Chart
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Helm
uses: azure/setup-helm@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Release Helm Chart
run: |
cd manifests/casdoor
REGISTRY=oci://registry-1.docker.io/casbin
helm package .
PKG_NAME=$(ls *.tgz)
helm repo index . --url $REGISTRY --merge index.yaml
helm push $PKG_NAME $REGISTRY
rm $PKG_NAME
- name: Commit updated helm index.yaml
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: 'ci: update helm index.yaml'

5
.gitignore vendored
View File

@ -18,7 +18,7 @@ bin/
.idea/
*.iml
.vscode/
.vscode/settings.json
tmp/
tmpFiles/
@ -31,3 +31,6 @@ commentsRouter*.go
# ignore build result
casdoor
server
# include helm-chart
!manifests/casdoor

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${workspaceFolder}",
"cwd": "${workspaceFolder}",
"debugAdapter": "dlv-dap",
"args": ["--createDatabase=true"]
}
]
}

View File

@ -86,6 +86,9 @@ docker-build: ## Build docker image with the manager.
docker-push: ## Push docker image with the manager.
docker push ${REGISTRY}/${IMG}:${IMG_TAG}
deps: ## Run dependencies for local development
docker compose up -d db
lint-install: ## Install golangci-lint
@# The following installs a specific version of golangci-lint, which is appropriate for a CI server to avoid different results from build to build
go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.40.1

View File

@ -69,6 +69,7 @@ https://casdoor.org
- By source code: https://casdoor.org/docs/basic/server-installation
- By Docker: https://casdoor.org/docs/basic/try-with-docker
- By Kubernetes Helm: https://casdoor.org/docs/basic/try-with-helm
## How to connect to Casdoor?

View File

@ -150,7 +150,7 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
func isAllowedInDemoMode(subOwner string, subName string, method string, urlPath string, objOwner string, objName string) bool {
if method == "POST" {
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" {
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") {
return true
} else if urlPath == "/api/update-user" {
// Allow ordinary users to update their own information

View File

@ -56,6 +56,17 @@ type Captcha struct {
SubType string `json:"subType"`
}
// this API is used by "Api URL" of Flarum's FoF Passport plugin
// https://github.com/FriendsOfFlarum/passport
type LaravelResponse struct {
Id string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
EmailVerifiedAt string `json:"email_verified_at"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Signup
// @Tag Login API
// @Title Signup
@ -418,7 +429,7 @@ func (c *ApiController) GetUserinfo() {
// @Title UserInfo2
// @Tag Account API
// @Description return Laravel compatible user information according to OAuth 2.0
// @Success 200 {object} LaravelResponse The Response object
// @Success 200 {object} controllers.LaravelResponse The Response object
// @router /user [get]
func (c *ApiController) GetUserinfo2() {
user, ok := c.RequireSignedInUser()
@ -426,17 +437,6 @@ func (c *ApiController) GetUserinfo2() {
return
}
// this API is used by "Api URL" of Flarum's FoF Passport plugin
// https://github.com/FriendsOfFlarum/passport
type LaravelResponse struct {
Id string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
EmailVerifiedAt string `json:"email_verified_at"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
response := LaravelResponse{
Id: user.Id,
Name: user.Name,

View File

@ -110,14 +110,6 @@ func (c *ApiController) GetApplication() {
}
}
// 0 as an initialization value, corresponding to the default configuration parameters
if application.FailedSigninLimit == 0 {
application.FailedSigninLimit = object.DefaultFailedSigninLimit
}
if application.FailedSigninfrozenTime == 0 {
application.FailedSigninfrozenTime = object.DefaultFailedSigninfrozenTime
}
c.ResponseOk(object.GetMaskedApplication(application, userId))
}

View File

@ -222,7 +222,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
// @Param redirectUri query string true "redirect uri"
// @Param scope query string true "scope"
// @Param state query string true "state"
// @Success 200 {object} Response The Response object
// @Success 200 {object} controllers.Response The Response object
// @router /get-app-login [get]
func (c *ApiController) GetApplicationLogin() {
clientId := c.Input().Get("clientId")
@ -342,7 +342,28 @@ func (c *ApiController) Login() {
return
}
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return
}
verificationCodeType := object.GetVerifyType(authForm.Username)
if verificationCodeType == object.VerifyTypeEmail && !application.IsCodeSigninViaEmailEnabled() {
c.ResponseError(c.T("auth:The login method: login with email is not enabled for the application"))
return
}
if verificationCodeType == object.VerifyTypePhone && !application.IsCodeSigninViaSmsEnabled() {
c.ResponseError(c.T("auth:The login method: login with SMS is not enabled for the application"))
return
}
var checkDest string
if verificationCodeType == object.VerifyTypePhone {
authForm.CountryCode = user.GetCountryCode(authForm.CountryCode)
@ -378,10 +399,14 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return
}
if !application.EnablePassword {
if authForm.SigninMethod == "Password" && !application.IsPasswordEnabled() {
c.ResponseError(c.T("auth:The login method: login with password is not enabled for the application"))
return
}
if authForm.SigninMethod == "LDAP" && !application.IsLdapEnabled() {
c.ResponseError(c.T("auth:The login method: login with LDAP is not enabled for the application"))
return
}
var enableCaptcha bool
if enableCaptcha, err = object.CheckToEnableCaptcha(application, authForm.Organization, authForm.Username); err != nil {
c.ResponseError(err.Error())
@ -411,7 +436,14 @@ func (c *ApiController) Login() {
}
password := authForm.Password
user, err = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
isSigninViaLdap := authForm.SigninMethod == "LDAP"
var isPasswordWithLdapEnabled bool
if authForm.SigninMethod == "Password" {
isPasswordWithLdapEnabled = application.IsPasswordWithLdapEnabled()
} else {
isPasswordWithLdapEnabled = false
}
user, err = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha, isSigninViaLdap, isPasswordWithLdapEnabled)
}
if err != nil {
@ -941,23 +973,27 @@ func (c *ApiController) GetWebhookEventType() {
// @router /api/get-captcha-status [get]
func (c *ApiController) GetCaptchaStatus() {
organization := c.Input().Get("organization")
userId := c.Input().Get("user_id")
userId := c.Input().Get("userId")
user, err := object.GetUserByFields(organization, userId)
if err != nil {
c.ResponseError(err.Error())
return
}
failedSigninLimit, _, err := object.GetFailedSigninConfigByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
captchaEnabled := false
if user != nil {
var failedSigninLimit int
failedSigninLimit, _, err = object.GetFailedSigninConfigByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
if user.SigninWrongTimes >= failedSigninLimit {
captchaEnabled = true
}
}
var captchaEnabled bool
if user != nil && user.SigninWrongTimes >= failedSigninLimit {
captchaEnabled = true
}
c.ResponseOk(captchaEnabled)
}

View File

@ -26,10 +26,11 @@ import (
// @Title Enforce
// @Tag Enforce API
// @Description Call Casbin Enforce API
// @Param body body object.CasbinRequest true "Casbin request"
// @Param body body []string true "Casbin request"
// @Param permissionId query string false "permission id"
// @Param modelId query string false "model id"
// @Param resourceId query string false "resource id"
// @Param owner query string false "owner"
// @Success 200 {object} controllers.Response The Response object
// @router /enforce [post]
func (c *ApiController) Enforce() {
@ -37,13 +38,14 @@ func (c *ApiController) Enforce() {
modelId := c.Input().Get("modelId")
resourceId := c.Input().Get("resourceId")
enforcerId := c.Input().Get("enforcerId")
owner := c.Input().Get("owner")
if len(c.Ctx.Input.RequestBody) == 0 {
c.ResponseError("The request body should not be empty")
return
}
var request object.CasbinRequest
var request []string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
if err != nil {
c.ResponseError(err.Error())
@ -60,7 +62,10 @@ func (c *ApiController) Enforce() {
res := []bool{}
keyRes := []string{}
enforceResult, err := enforcer.Enforce(request...)
// type transformation
interfaceRequest := util.StringToInterfaceArray(request)
enforceResult, err := enforcer.Enforce(interfaceRequest...)
if err != nil {
c.ResponseError(err.Error())
return
@ -87,7 +92,7 @@ func (c *ApiController) Enforce() {
res := []bool{}
keyRes := []string{}
enforceResult, err := object.Enforce(permission, &request)
enforceResult, err := object.Enforce(permission, request)
if err != nil {
c.ResponseError(err.Error())
return
@ -114,6 +119,8 @@ func (c *ApiController) Enforce() {
c.ResponseError(err.Error())
return
}
} else if owner != "" {
permissions, err = object.GetPermissions(owner)
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
@ -129,7 +136,7 @@ func (c *ApiController) Enforce() {
return
}
enforceResult, err := object.Enforce(firstPermission, &request, permissionIds...)
enforceResult, err := object.Enforce(firstPermission, request, permissionIds...)
if err != nil {
c.ResponseError(err.Error())
return
@ -146,17 +153,19 @@ func (c *ApiController) Enforce() {
// @Title BatchEnforce
// @Tag Enforce API
// @Description Call Casbin BatchEnforce API
// @Param body body object.CasbinRequest true "array of casbin requests"
// @Param body body []string true "array of casbin requests"
// @Param permissionId query string false "permission id"
// @Param modelId query string false "model id"
// @Param owner query string false "owner"
// @Success 200 {object} controllers.Response The Response object
// @router /batch-enforce [post]
func (c *ApiController) BatchEnforce() {
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
enforcerId := c.Input().Get("enforcerId")
owner := c.Input().Get("owner")
var requests []object.CasbinRequest
var requests [][]string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
if err != nil {
c.ResponseError(err.Error())
@ -173,7 +182,10 @@ func (c *ApiController) BatchEnforce() {
res := [][]bool{}
keyRes := []string{}
enforceResult, err := enforcer.BatchEnforce(requests)
// type transformation
interfaceRequests := util.StringToInterfaceArray2d(requests)
enforceResult, err := enforcer.BatchEnforce(interfaceRequests)
if err != nil {
c.ResponseError(err.Error())
return
@ -200,7 +212,7 @@ func (c *ApiController) BatchEnforce() {
res := [][]bool{}
keyRes := []string{}
enforceResult, err := object.BatchEnforce(permission, &requests)
enforceResult, err := object.BatchEnforce(permission, requests)
if err != nil {
c.ResponseError(err.Error())
return
@ -221,6 +233,8 @@ func (c *ApiController) BatchEnforce() {
c.ResponseError(err.Error())
return
}
} else if owner != "" {
permissions, err = object.GetPermissions(owner)
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
@ -236,7 +250,7 @@ func (c *ApiController) BatchEnforce() {
return
}
enforceResult, err := object.BatchEnforce(firstPermission, &requests, permissionIds...)
enforceResult, err := object.BatchEnforce(firstPermission, requests, permissionIds...)
if err != nil {
c.ResponseError(err.Error())
return
@ -250,10 +264,13 @@ func (c *ApiController) BatchEnforce() {
}
func (c *ApiController) GetAllObjects() {
userId := c.GetSessionUsername()
userId := c.Input().Get("userId")
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
userId = c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
}
objects, err := object.GetAllObjects(userId)
@ -266,10 +283,13 @@ func (c *ApiController) GetAllObjects() {
}
func (c *ApiController) GetAllActions() {
userId := c.GetSessionUsername()
userId := c.Input().Get("userId")
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
userId = c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
}
actions, err := object.GetAllActions(userId)
@ -282,10 +302,13 @@ func (c *ApiController) GetAllActions() {
}
func (c *ApiController) GetAllRoles() {
userId := c.GetSessionUsername()
userId := c.Input().Get("userId")
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
userId = c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
}
roles, err := object.GetAllRoles(userId)

View File

@ -138,7 +138,7 @@ func (c *ApiController) AddEnforcer() {
// @Title DeleteEnforcer
// @Tag Enforcer API
// @Description delete enforcer
// @Param body body object.Enforce true "The enforcer object"
// @Param body body object.Enforcer true "The enforcer object"
// @Success 200 {object} object.Enforcer
// @router /delete-enforcer [post]
func (c *ApiController) DeleteEnforcer() {

164
controllers/invitation.go Normal file
View File

@ -0,0 +1,164 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetInvitations
// @Title GetInvitations
// @Tag Invitation API
// @Description get invitations
// @Param owner query string true "The owner of invitations"
// @Success 200 {array} object.Invitation The Response object
// @router /get-invitations [get]
func (c *ApiController) GetInvitations() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
invitations, err := object.GetInvitations(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(invitations)
} else {
limit := util.ParseInt(limit)
count, err := object.GetInvitationCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
invitations, err := object.GetPaginationInvitations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(invitations, paginator.Nums())
}
}
// GetInvitation
// @Title GetInvitation
// @Tag Invitation API
// @Description get invitation
// @Param id query string true "The id ( owner/name ) of the invitation"
// @Success 200 {object} object.Invitation The Response object
// @router /get-invitation [get]
func (c *ApiController) GetInvitation() {
id := c.Input().Get("id")
invitation, err := object.GetInvitation(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(invitation)
}
// UpdateInvitation
// @Title UpdateInvitation
// @Tag Invitation API
// @Description update invitation
// @Param id query string true "The id ( owner/name ) of the invitation"
// @Param body body object.Invitation true "The details of the invitation"
// @Success 200 {object} controllers.Response The Response object
// @router /update-invitation [post]
func (c *ApiController) UpdateInvitation() {
id := c.Input().Get("id")
var invitation object.Invitation
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateInvitation(id, &invitation))
c.ServeJSON()
}
// AddInvitation
// @Title AddInvitation
// @Tag Invitation API
// @Description add invitation
// @Param body body object.Invitation true "The details of the invitation"
// @Success 200 {object} controllers.Response The Response object
// @router /add-invitation [post]
func (c *ApiController) AddInvitation() {
var invitation object.Invitation
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddInvitation(&invitation))
c.ServeJSON()
}
// DeleteInvitation
// @Title DeleteInvitation
// @Tag Invitation API
// @Description delete invitation
// @Param body body object.Invitation true "The details of the invitation"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-invitation [post]
func (c *ApiController) DeleteInvitation() {
var invitation object.Invitation
err := json.Unmarshal(c.Ctx.Input.RequestBody, &invitation)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteInvitation(&invitation))
c.ServeJSON()
}
// VerifyInvitation
// @Title VerifyInvitation
// @Tag Invitation API
// @Description verify invitation
// @Param id query string true "The id ( owner/name ) of the invitation"
// @Success 200 {object} controllers.Response The Response object
// @router /verify-invitation [get]
func (c *ApiController) VerifyInvitation() {
id := c.Input().Get("id")
payment, attachInfo, err := object.VerifyInvitation(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payment, attachInfo)
}

View File

@ -42,7 +42,7 @@ type LdapSyncResp struct {
// @Tag Account API
// @Description get ldap users
// Param id string true "id"
// @Success 200 {object} LdapResp The Response object
// @Success 200 {object} controllers.LdapResp The Response object
// @router /get-ldap-users [get]
func (c *ApiController) GetLdapUsers() {
id := c.Input().Get("id")
@ -250,7 +250,7 @@ func (c *ApiController) DeleteLdap() {
// @Tag Account API
// @Description sync ldap users
// @Param id query string true "id"
// @Success 200 {object} LdapSyncResp The Response object
// @Success 200 {object} controllers.LdapSyncResp The Response object
// @router /sync-ldap-users [post]
func (c *ApiController) SyncLdapUsers() {
id := c.Input().Get("id")

View File

@ -73,7 +73,7 @@ func (c *ApiController) MfaSetupInitiate() {
// @Description setup verify totp
// @param secret form string true "MFA secret"
// @param passcode form string true "MFA passcode"
// @Success 200 {object} Response object
// @Success 200 {object} controllers.Response The Response object
// @router /mfa/setup/verify [post]
func (c *ApiController) MfaSetupVerify() {
mfaType := c.Ctx.Request.Form.Get("mfaType")
@ -104,7 +104,7 @@ func (c *ApiController) MfaSetupVerify() {
// @param owner form string true "owner of user"
// @param name form string true "name of user"
// @param type form string true "MFA auth type"
// @Success 200 {object} Response object
// @Success 200 {object} controllers.Response The Response object
// @router /mfa/setup/enable [post]
func (c *ApiController) MfaSetupEnable() {
owner := c.Ctx.Request.Form.Get("owner")
@ -143,7 +143,7 @@ func (c *ApiController) MfaSetupEnable() {
// @Description: Delete MFA
// @param owner form string true "owner of user"
// @param name form string true "name of user"
// @Success 200 {object} Response object
// @Success 200 {object} controllers.Response The Response object
// @router /delete-mfa/ [post]
func (c *ApiController) DeleteMfa() {
owner := c.Ctx.Request.Form.Get("owner")
@ -176,7 +176,7 @@ func (c *ApiController) DeleteMfa() {
// @param owner form string true "owner of user"
// @param name form string true "name of user"
// @param id form string true "id of user's MFA props"
// @Success 200 {object} Response object
// @Success 200 {object} controllers.Response The Response object
// @router /set-preferred-mfa [post]
func (c *ApiController) SetPreferredMfa() {
mfaType := c.Ctx.Request.Form.Get("mfaType")

View File

@ -178,7 +178,7 @@ func (c *ApiController) DeleteOrganization() {
// @Tag Organization API
// @Description get default application
// @Param id query string true "organization id"
// @Success 200 {object} Response The Response object
// @Success 200 {object} controllers.Response The Response object
// @router /get-default-application [get]
func (c *ApiController) GetDefaultApplication() {
userId := c.GetSessionUsername()

View File

@ -51,9 +51,14 @@ type NotificationForm struct {
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param from body controllers.EmailForm true "Details of the email request"
// @Success 200 {object} Response object
// @Success 200 {object} controllers.Response The Response object
// @router /api/send-email [post]
func (c *ApiController) SendEmail() {
userId, ok := c.RequireSignedIn()
if !ok {
return
}
var emailForm EmailForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
@ -108,8 +113,22 @@ func (c *ApiController) SendEmail() {
}
code := "123456"
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := fmt.Sprintf(emailForm.Content, code)
content := strings.Replace(provider.Content, "%s", code, 1)
if !strings.HasPrefix(userId, "app/") {
var user *object.User
user, err = object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user != nil {
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
}
}
for _, receiver := range emailForm.Receivers {
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
if err != nil {
@ -128,7 +147,7 @@ func (c *ApiController) SendEmail() {
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param from body controllers.SmsForm true "Details of the sms request"
// @Success 200 {object} Response object
// @Success 200 {object} controllers.Response The Response object
// @router /api/send-sms [post]
func (c *ApiController) SendSms() {
provider, err := c.GetProviderFromContext("SMS")
@ -166,7 +185,7 @@ func (c *ApiController) SendSms() {
// @Tag Service API
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
// @Param from body controllers.NotificationForm true "Details of the notification request"
// @Success 200 {object} Response object
// @Success 200 {object} controllers.Response The Response object
// @router /api/send-notification [post]
func (c *ApiController) SendNotification() {
provider, err := c.GetProviderFromContext("Notification")

View File

@ -15,7 +15,8 @@
package form
type AuthForm struct {
Type string `json:"type"`
Type string `json:"type"`
SigninMethod string `json:"signinMethod"`
Organization string `json:"organization"`
Username string `json:"username"`

2
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/casdoor/go-sms-sender v0.19.0
github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/notify v0.45.0
github.com/casdoor/oss v1.4.1
github.com/casdoor/oss v1.5.0
github.com/casdoor/xorm-adapter/v3 v3.1.0
github.com/casvisor/casvisor-go-sdk v1.0.3
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f

2
go.sum
View File

@ -1091,6 +1091,8 @@ github.com/casdoor/notify v0.45.0 h1:OlaFvcQFjGOgA4mRx07M8AH1gvb5xNo21mcqrVGlLgk
github.com/casdoor/notify v0.45.0/go.mod h1:wNHQu0tiDROMBIvz0j3Om3Lhd5yZ+AIfnFb8MYb8OLQ=
github.com/casdoor/oss v1.4.1 h1:/P2JCyGzB2TtpJ3LocKocI1VAme2YdvVau2wpMQGt7I=
github.com/casdoor/oss v1.4.1/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE=
github.com/casdoor/oss v1.5.0 h1:mi1htaXR5fynskDry1S3wk+Dd2nRY1z1pVcnGsqMqP4=
github.com/casdoor/oss v1.5.0/go.mod h1:rJAWA0hLhtu94t6IRpotLUkXO1NWMASirywQYaGizJE=
github.com/casdoor/xorm-adapter/v3 v3.1.0 h1:NodWayRtSLVSeCvL9H3Hc61k0G17KhV9IymTCNfh3kk=
github.com/casdoor/xorm-adapter/v3 v3.1.0/go.mod h1:4WTcUw+bTgBylGHeGHzTtBvuTXRS23dtwzFLl9tsgFM=
github.com/casvisor/casvisor-go-sdk v1.0.3 h1:TKJQWKnhtznEBhzLPEdNsp7nJK2GgdD8JsB0lFPMW7U=

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Das Konto für den Anbieter %s und Benutzernamen %s (%s) existiert nicht und es ist nicht erlaubt, ein neues Konto anzumelden. Bitte wenden Sie sich an Ihren IT-Support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Das Konto für den Anbieter %s und Benutzernamen %s (%s) ist bereits mit einem anderen Konto verknüpft: %s (%s)",
"The application: %s does not exist": "Die Anwendung: %s existiert nicht",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "Die Anmeldeart \"Anmeldung mit Passwort\" ist für die Anwendung nicht aktiviert",
"The provider: %s is not enabled for the application": "Der Anbieter: %s ist nicht für die Anwendung aktiviert",
"Unauthorized operation": "Nicht autorisierte Operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "La cuenta para el proveedor: %s y el nombre de usuario: %s (%s) no existe y no se permite registrarse como una nueva cuenta, por favor contacte a su soporte de TI",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "La cuenta para proveedor: %s y nombre de usuario: %s (%s) ya está vinculada a otra cuenta: %s (%s)",
"The application: %s does not exist": "La aplicación: %s no existe",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "El método de inicio de sesión: inicio de sesión con contraseña no está habilitado para la aplicación",
"The provider: %s is not enabled for the application": "El proveedor: %s no está habilitado para la aplicación",
"Unauthorized operation": "Operación no autorizada",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Le compte pour le fournisseur : %s et le nom d'utilisateur : %s (%s) n'existe pas et n'est pas autorisé à s'inscrire comme nouveau compte, veuillez contacter votre support informatique",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Le compte du fournisseur : %s et le nom d'utilisateur : %s (%s) sont déjà liés à un autre compte : %s (%s)",
"The application: %s does not exist": "L'application : %s n'existe pas",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "La méthode de connexion : connexion avec mot de passe n'est pas activée pour l'application",
"The provider: %s is not enabled for the application": "Le fournisseur :%s n'est pas activé pour l'application",
"Unauthorized operation": "Opération non autorisée",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Akun untuk penyedia: %s dan nama pengguna: %s (%s) tidak ada dan tidak diizinkan untuk mendaftar sebagai akun baru, silakan hubungi dukungan IT Anda",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Akun untuk provider: %s dan username: %s (%s) sudah terhubung dengan akun lain: %s (%s)",
"The application: %s does not exist": "Aplikasi: %s tidak ada",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "Metode login: login dengan kata sandi tidak diaktifkan untuk aplikasi tersebut",
"The provider: %s is not enabled for the application": "Penyedia: %s tidak diaktifkan untuk aplikasi ini",
"Unauthorized operation": "Operasi tidak sah",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "プロバイダー名:%sとユーザー名%s%sのアカウントは存在しません。新しいアカウントとしてサインアップすることはできません。 ITサポートに連絡してください",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "プロバイダのアカウント:%s とユーザー名:%s (%s) は既に別のアカウント:%s (%s) にリンクされています",
"The application: %s does not exist": "アプリケーション: %sは存在しません",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "ログイン方法:パスワードでのログインはアプリケーションで有効になっていません",
"The provider: %s is not enabled for the application": "プロバイダー:%sはアプリケーションでは有効化されていません",
"Unauthorized operation": "不正操作",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "공급자 계정 %s과 사용자 이름 %s (%s)는 존재하지 않으며 새 계정으로 등록할 수 없습니다. IT 지원팀에 문의하십시오",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "공급자 계정 %s과 사용자 이름 %s(%s)는 이미 다른 계정 %s(%s)에 연결되어 있습니다",
"The application: %s does not exist": "해당 애플리케이션(%s)이 존재하지 않습니다",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "어플리케이션에서는 암호를 사용한 로그인 방법이 활성화되어 있지 않습니다",
"The provider: %s is not enabled for the application": "제공자 %s은(는) 응용 프로그램에서 활성화되어 있지 않습니다",
"Unauthorized operation": "무단 조작",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Аккаунт для провайдера: %s и имя пользователя: %s (%s) не существует и не может быть зарегистрирован как новый аккаунт. Пожалуйста, обратитесь в службу поддержки IT",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Аккаунт поставщика: %s и имя пользователя: %s (%s) уже связаны с другим аккаунтом: %s (%s)",
"The application: %s does not exist": "Приложение: %s не существует",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
"Unauthorized operation": "Несанкционированная операция",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)",
"The application: %s does not exist": "The application: %s does not exist",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The provider: %s is not enabled for the application": "The provider: %s is not enabled for the application",
"Unauthorized operation": "Unauthorized operation",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Tài khoản cho nhà cung cấp: %s và tên người dùng: %s (%s) không tồn tại và không được phép đăng ký như một tài khoản mới, vui lòng liên hệ với bộ phận hỗ trợ công nghệ thông tin của bạn",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Tài khoản cho nhà cung cấp: %s và tên người dùng: %s (%s) đã được liên kết với tài khoản khác: %s (%s)",
"The application: %s does not exist": "Ứng dụng: %s không tồn tại",
"The login method: login with LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with password is not enabled for the application": "Phương thức đăng nhập: đăng nhập bằng mật khẩu không được kích hoạt cho ứng dụng",
"The provider: %s is not enabled for the application": "Nhà cung cấp: %s không được kích hoạt cho ứng dụng",
"Unauthorized operation": "Hoạt động không được ủy quyền",

View File

@ -15,6 +15,9 @@
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "提供商账户: %s 与用户名: %s (%s) 不存在且 不允许注册新账户, 请联系IT支持",
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "提供商账户: %s与用户名: %s (%s)已经与其他账户绑定: %s (%s)",
"The application: %s does not exist": "应用%s不存在",
"The login method: login with LDAP is not enabled for the application": "该应用禁止采用LDAP登录方式",
"The login method: login with SMS is not enabled for the application": "该应用禁止采用短信登录方式",
"The login method: login with email is not enabled for the application": "该应用禁止采用邮箱登录方式",
"The login method: login with password is not enabled for the application": "该应用禁止采用密码登录方式",
"The provider: %s is not enabled for the application": "该应用的提供商: %s未被启用",
"Unauthorized operation": "未授权的操作",

View File

@ -45,6 +45,23 @@
"alertType": "None"
}
],
"signinMethods": [
{
"name": "Password",
"displayName": "Password",
"rule": "All",
},
{
"name": "Verification code",
"displayName": "Verification code",
"rule": "All",
},
{
"name": "WebAuthn",
"displayName": "WebAuthn",
"rule": "None",
},
],
"signupItems": [
{
"name": "ID",
@ -106,7 +123,7 @@
"redirectUris": [""],
"expireInHours": 168,
"failedSigninLimit": 5,
"failedSigninfrozenTime": 15
"failedSigninFrozenTime": 15
}
],
"users": [

View File

@ -1,23 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/

View File

@ -1,24 +0,0 @@
apiVersion: v2
name: casdoor-helm-charts
description: A Helm chart for Kubernetes
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.3.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "1.18.0"

View File

@ -1,22 +0,0 @@
1. Get the application URL by running these commands:
{{- if .Values.ingress.enabled }}
{{- range $host := .Values.ingress.hosts }}
{{- range .paths }}
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
{{- end }}
{{- end }}
{{- else if contains "NodePort" .Values.service.type }}
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "casdoor.fullname" . }})
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
echo http://$NODE_IP:$NODE_PORT
{{- else if contains "LoadBalancer" .Values.service.type }}
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "casdoor.fullname" . }}'
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "casdoor.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
echo http://$SERVICE_IP:{{ .Values.service.port }}
{{- else if contains "ClusterIP" .Values.service.type }}
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "casdoor.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT
{{- end }}

View File

@ -1,62 +0,0 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "casdoor.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "casdoor.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "casdoor.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "casdoor.labels" -}}
helm.sh/chart: {{ include "casdoor.chart" . }}
{{ include "casdoor.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "casdoor.selectorLabels" -}}
app.kubernetes.io/name: {{ include "casdoor.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "casdoor.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "casdoor.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

View File

@ -1,8 +0,0 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ printf "%s-config" (include "casdoor.fullname" .) }}
labels:
{{- include "casdoor.labels" . | nindent 4 }}
data:
app.conf: {{ tpl .Values.config . | toYaml | nindent 4 }}

View File

@ -1,86 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "casdoor.fullname" . }}
labels:
{{- include "casdoor.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "casdoor.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ tpl .Values.config . | toYaml | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "casdoor.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "casdoor.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}/{{ .Values.image.name }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
# command: ["sleep", "100000000"]
env:
- name: RUNNING_IN_DOCKER
value: "true"
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
{{ if .Values.probe.liveness.enabled }}
livenessProbe:
httpGet:
path: /
port: http
{{ end }}
{{ if .Values.probe.readiness.enabled }}
readinessProbe:
httpGet:
path: /
port: http
{{ end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
volumeMounts:
- name: config-volume
mountPath: /conf
{{ if .Values.extraContainersEnabled }}
{{- .Values.extraContainers | nindent 8 }}
{{- end }}
volumes:
- name: config-volume
projected:
defaultMode: 420
sources:
- configMap:
items:
- key: app.conf
path: app.conf
name: {{ printf "%s-config" (include "casdoor.fullname" .) }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}

View File

@ -1,28 +0,0 @@
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2beta1
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "casdoor.fullname" . }}
labels:
{{- include "casdoor.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "casdoor.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
- type: Resource
resource:
name: cpu
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
- type: Resource
resource:
name: memory
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
{{- end }}
{{- end }}

View File

@ -1,61 +0,0 @@
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "casdoor.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
{{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
{{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
{{- end }}
{{- end }}
{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1
{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
{{- else -}}
apiVersion: extensions/v1beta1
{{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
labels:
{{- include "casdoor.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
pathType: {{ .pathType }}
{{- end }}
backend:
{{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
service:
name: {{ $fullName }}
port:
number: {{ $svcPort }}
{{- else }}
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}

View File

@ -1,15 +0,0 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "casdoor.fullname" . }}
labels:
{{- include "casdoor.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
selector:
{{- include "casdoor.selectorLabels" . | nindent 4 }}

View File

@ -1,12 +0,0 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "casdoor.serviceAccountName" . }}
labels:
{{- include "casdoor.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}

View File

@ -1,15 +0,0 @@
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "casdoor.fullname" . }}-test-connection"
labels:
{{- include "casdoor.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
spec:
containers:
- name: wget
image: busybox
command: ['wget']
args: ['{{ include "casdoor.fullname" . }}:{{ .Values.service.port }}']
restartPolicy: Never

View File

@ -1,117 +0,0 @@
# Default values for casdoor.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: casbin
name: casdoor
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: ""
# ref: https://casdoor.org/docs/basic/server-installation#via-ini-file
config: |
appname = casdoor
httpport = {{ .Values.service.port }}
runmode = dev
SessionOn = true
copyrequestbody = true
driverName = sqlite
dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1"
dbName = casdoor
redisEndpoint =
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
socks5Proxy = ""
verificationCodeTimeout = 10
initScore = 0
logPostOnly = true
origin =
enableGzip = true
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
probe:
readiness:
enabled: true
liveness:
enabled: true
service:
type: ClusterIP
port: 8000
ingress:
enabled: false
className: ""
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}
# -- Optionally add extra sidecar containers.
extraContainersEnabled: false
extraContainers: ""
# extraContainers: |
# - name: ...
# image: ...

View File

@ -24,6 +24,12 @@ import (
"github.com/xorm-io/core"
)
type SigninMethod struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Rule string `json:"rule"`
}
type SignupItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
@ -31,12 +37,13 @@ type SignupItem struct {
Prompted bool `json:"prompted"`
Label string `json:"label"`
Placeholder string `json:"placeholder"`
Regex string `json:"regex"`
Rule string `json:"rule"`
}
type SamlItem struct {
Name string `json:"name"`
NameFormat string `json:"nameformat"`
NameFormat string `json:"nameFormat"`
Value string `json:"value"`
}
@ -63,6 +70,7 @@ type Application struct {
OrgChoiceMode string `json:"orgChoiceMode"`
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SigninMethods []*SigninMethod `xorm:"varchar(2000)" json:"signinMethods"`
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
@ -75,6 +83,7 @@ type Application struct {
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
RedirectUris []string `xorm:"varchar(1000)" json:"redirectUris"`
TokenFormat string `xorm:"varchar(100)" json:"tokenFormat"`
TokenFields []string `xorm:"varchar(1000)" json:"tokenFields"`
ExpireInHours int `json:"expireInHours"`
RefreshExpireInHours int `json:"refreshExpireInHours"`
SignupUrl string `xorm:"varchar(200)" json:"signupUrl"`
@ -92,7 +101,7 @@ type Application struct {
FormBackgroundUrl string `xorm:"varchar(200)" json:"formBackgroundUrl"`
FailedSigninLimit int `json:"failedSigninLimit"`
FailedSigninfrozenTime int `json:"failedSigninfrozenTime"`
FailedSigninFrozenTime int `json:"failedSigninFrozenTime"`
}
func GetApplicationCount(owner, field, value string) (int64, error) {
@ -191,6 +200,30 @@ func extendApplicationWithOrg(application *Application) (err error) {
return
}
func extendApplicationWithSigninMethods(application *Application) (err error) {
if len(application.SigninMethods) == 0 {
if application.EnablePassword {
signinMethod := &SigninMethod{Name: "Password", DisplayName: "Password", Rule: "All"}
application.SigninMethods = append(application.SigninMethods, signinMethod)
}
if application.EnableCodeSignin {
signinMethod := &SigninMethod{Name: "Verification code", DisplayName: "Verification code", Rule: "All"}
application.SigninMethods = append(application.SigninMethods, signinMethod)
}
if application.EnableWebAuthn {
signinMethod := &SigninMethod{Name: "WebAuthn", DisplayName: "WebAuthn", Rule: "None"}
application.SigninMethods = append(application.SigninMethods, signinMethod)
}
}
if len(application.SigninMethods) == 0 {
signinMethod := &SigninMethod{Name: "Password", DisplayName: "Password", Rule: "All"}
application.SigninMethods = append(application.SigninMethods, signinMethod)
}
return
}
func getApplication(owner string, name string) (*Application, error) {
if owner == "" || name == "" {
return nil, nil
@ -213,6 +246,11 @@ func getApplication(owner string, name string) (*Application, error) {
return nil, err
}
err = extendApplicationWithSigninMethods(&application)
if err != nil {
return nil, err
}
return &application, nil
} else {
return nil, nil
@ -237,6 +275,11 @@ func GetApplicationByOrganizationName(organization string) (*Application, error)
return nil, err
}
err = extendApplicationWithSigninMethods(&application)
if err != nil {
return nil, err
}
return &application, nil
} else {
return nil, nil
@ -284,6 +327,11 @@ func GetApplicationByClientId(clientId string) (*Application, error) {
return nil, err
}
err = extendApplicationWithSigninMethods(&application)
if err != nil {
return nil, err
}
return &application, nil
} else {
return nil, nil
@ -300,6 +348,17 @@ func GetMaskedApplication(application *Application, userId string) *Application
return nil
}
if application.TokenFields == nil {
application.TokenFields = []string{}
}
if application.FailedSigninLimit == 0 {
application.FailedSigninLimit = DefaultFailedSigninLimit
}
if application.FailedSigninFrozenTime == 0 {
application.FailedSigninFrozenTime = DefaultFailedSigninFrozenTime
}
if userId != "" {
if isUserIdGlobalAdmin(userId) {
return application
@ -485,6 +544,69 @@ func (application *Application) IsRedirectUriValid(redirectUri string) bool {
return false
}
func (application *Application) IsPasswordEnabled() bool {
if len(application.SigninMethods) == 0 {
return application.EnablePassword
} else {
for _, signinMethod := range application.SigninMethods {
if signinMethod.Name == "Password" {
return true
}
}
return false
}
}
func (application *Application) IsPasswordWithLdapEnabled() bool {
if len(application.SigninMethods) == 0 {
return application.EnablePassword
} else {
for _, signinMethod := range application.SigninMethods {
if signinMethod.Name == "Password" && signinMethod.Rule == "All" {
return true
}
}
return false
}
}
func (application *Application) IsCodeSigninViaEmailEnabled() bool {
if len(application.SigninMethods) == 0 {
return application.EnableCodeSignin
} else {
for _, signinMethod := range application.SigninMethods {
if signinMethod.Name == "Verification code" && signinMethod.Rule != "Phone only" {
return true
}
}
return false
}
}
func (application *Application) IsCodeSigninViaSmsEnabled() bool {
if len(application.SigninMethods) == 0 {
return application.EnableCodeSignin
} else {
for _, signinMethod := range application.SigninMethods {
if signinMethod.Name == "Verification code" && signinMethod.Rule != "Email only" {
return true
}
}
return false
}
}
func (application *Application) IsLdapEnabled() bool {
if len(application.SigninMethods) > 0 {
for _, signinMethod := range application.SigninMethods {
if signinMethod.Name == "LDAP" {
return true
}
}
}
return false
}
func IsOriginAllowed(origin string) (bool, error) {
applications, err := GetApplications("")
if err != nil {
@ -579,7 +701,7 @@ func applicationChangeTrigger(oldName string, newName string) error {
}
}
permissions[i].Resources = permissionResoureces
_, err = session.Where("name=?", permissions[i].Name).Update(permissions[i])
_, err = session.Where("owner=?", permissions[i].Owner).Where("name=?", permissions[i].Name).Update(permissions[i])
if err != nil {
return err
}

View File

@ -28,9 +28,8 @@ import (
)
const (
DefaultFailedSigninLimit = 5
// DefaultFailedSigninfrozenTime The unit of frozen time is minutes
DefaultFailedSigninfrozenTime = 15
DefaultFailedSigninLimit = 5
DefaultFailedSigninFrozenTime = 15
)
func CheckUserSignup(application *Application, organization *Organization, form *form.AuthForm, lang string) string {
@ -144,7 +143,7 @@ func CheckUserSignup(application *Application, organization *Organization, form
}
func checkSigninErrorTimes(user *User, lang string) error {
failedSigninLimit, failedSigninfrozenTime, err := GetFailedSigninConfigByUser(user)
failedSigninLimit, failedSigninFrozenTime, err := GetFailedSigninConfigByUser(user)
if err != nil {
return err
}
@ -152,7 +151,7 @@ func checkSigninErrorTimes(user *User, lang string) error {
if user.SigninWrongTimes >= failedSigninLimit {
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
minutes := failedSigninfrozenTime - int(passedTime.Minutes())
minutes := failedSigninFrozenTime - int(passedTime.Minutes())
// deny the login if the error times is greater than the limit and the last login time is less than the duration
if minutes > 0 {
@ -278,8 +277,12 @@ func checkLdapUserPassword(user *User, password string, lang string) error {
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, error) {
enableCaptcha := false
isSigninViaLdap := false
isPasswordWithLdapEnabled := false
if len(options) > 0 {
enableCaptcha = options[0]
isSigninViaLdap = options[1]
isPasswordWithLdapEnabled = options[2]
}
user, err := GetUserByFields(organization, username)
if err != nil {
@ -294,14 +297,33 @@ func CheckUserPassword(organization string, username string, password string, la
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator"))
}
if isSigninViaLdap {
if user.Ldap == "" {
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
}
}
if user.Ldap != "" {
if !isSigninViaLdap && !isPasswordWithLdapEnabled {
return nil, fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect"))
}
// check the login error times
if !enableCaptcha {
err = checkSigninErrorTimes(user, lang)
if err != nil {
return nil, err
}
}
// only for LDAP users
err = checkLdapUserPassword(user, password, lang)
if err != nil {
if err.Error() == "user not exist" {
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
}
return nil, err
return nil, recordSigninErrorInfo(user, lang, enableCaptcha)
}
} else {
err = CheckPassword(user, password, lang, enableCaptcha)
@ -486,12 +508,11 @@ func CheckToEnableCaptcha(application *Application, organization, username strin
return false, err
}
var failedSigninLimit int
if application.FailedSigninLimit == 0 {
failedSigninLimit = 5
} else {
failedSigninLimit = application.FailedSigninLimit
failedSigninLimit := application.FailedSigninLimit
if failedSigninLimit == 0 {
failedSigninLimit = DefaultFailedSigninLimit
}
return user != nil && user.SigninWrongTimes >= failedSigninLimit, nil
}
return providerItem.Rule == "Always", nil

View File

@ -52,18 +52,18 @@ func GetFailedSigninConfigByUser(user *User) (int, int, error) {
if err != nil {
return 0, 0, err
}
failedSigninLimit := application.FailedSigninLimit
failedSigninfrozenTime := application.FailedSigninfrozenTime
// 0 as an initialization value, corresponding to the default configuration parameters
failedSigninLimit := application.FailedSigninLimit
if failedSigninLimit == 0 {
failedSigninLimit = DefaultFailedSigninLimit
}
if failedSigninfrozenTime == 0 {
failedSigninfrozenTime = DefaultFailedSigninfrozenTime
failedSigninFrozenTime := application.FailedSigninFrozenTime
if failedSigninFrozenTime == 0 {
failedSigninFrozenTime = DefaultFailedSigninFrozenTime
}
return failedSigninLimit, failedSigninfrozenTime, nil
return failedSigninLimit, failedSigninFrozenTime, nil
}
func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
@ -72,7 +72,7 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
enableCaptcha = options[0]
}
failedSigninLimit, failedSigninfrozenTime, errSignin := GetFailedSigninConfigByUser(user)
failedSigninLimit, failedSigninFrozenTime, errSignin := GetFailedSigninConfigByUser(user)
if errSignin != nil {
return errSignin
}
@ -101,5 +101,5 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
}
// don't show the chance error message if the user has no chance left
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), failedSigninfrozenTime)
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), failedSigninFrozenTime)
}

View File

@ -18,6 +18,7 @@ import (
"errors"
"fmt"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/builder"
"github.com/xorm-io/core"
@ -224,7 +225,8 @@ func GetGroupUserCount(groupId string, field, value string) (int64, error) {
if field == "" && value == "" {
return int64(len(names)), nil
} else {
return ormer.Engine.Table("user").
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
return ormer.Engine.Table(tableNamePrefix+"user").
Where("owner = ?", owner).In("name", names).
And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count()
@ -239,7 +241,9 @@ func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, so
return nil, err
}
session := ormer.Engine.Table("user").
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
prefixedUserTable := tableNamePrefix + "user"
session := ormer.Engine.Table(prefixedUserTable).
Where("owner = ?", owner).In("name", names)
if offset != -1 && limit != -1 {
@ -247,16 +251,19 @@ func GetPaginationGroupUsers(groupId string, offset, limit int, field, value, so
}
if field != "" && value != "" {
session = session.And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%")
session = session.And(fmt.Sprintf("%s.%s like ?", prefixedUserTable, util.CamelToSnakeCase(field)), "%"+value+"%")
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
orderQuery := fmt.Sprintf("%s.%s", prefixedUserTable, util.SnakeString(sortField))
if sortOrder == "ascend" {
session = session.Asc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
session = session.Asc(orderQuery)
} else {
session = session.Desc(fmt.Sprintf("user.%s", util.SnakeString(sortField)))
session = session.Desc(orderQuery)
}
err = session.Find(&users)

View File

@ -180,6 +180,11 @@ func initBuiltInApplication() {
Providers: []*ProviderItem{
{Name: "provider_captcha_default", CanSignUp: false, CanSignIn: false, CanUnlink: false, Prompted: false, SignupGroup: "", Rule: "None", Provider: nil},
},
SigninMethods: []*SigninMethod{
{Name: "Password", DisplayName: "Password", Rule: "None"},
{Name: "Verification code", DisplayName: "Verification code", Rule: "All"},
{Name: "WebAuthn", DisplayName: "WebAuthn", Rule: "None"},
},
SignupItems: []*SignupItem{
{Name: "ID", Visible: false, Required: true, Prompted: false, Rule: "Random"},
{Name: "Username", Visible: true, Required: true, Prompted: false, Rule: "None"},
@ -192,6 +197,7 @@ func initBuiltInApplication() {
},
Tags: []string{},
RedirectUris: []string{},
TokenFields: []string{},
ExpireInHours: 168,
FormOffset: 2,
}

View File

@ -136,17 +136,23 @@ func readInitDataFromFile(filePath string) (*InitData, error) {
if application.Providers == nil {
application.Providers = []*ProviderItem{}
}
if application.SigninMethods == nil {
application.SigninMethods = []*SigninMethod{}
}
if application.SignupItems == nil {
application.SignupItems = []*SignupItem{}
}
if application.GrantTypes == nil {
application.GrantTypes = []string{}
}
if application.Tags == nil {
application.Tags = []string{}
}
if application.RedirectUris == nil {
application.RedirectUris = []string{}
}
if application.Tags == nil {
application.Tags = []string{}
if application.TokenFields == nil {
application.TokenFields = []string{}
}
}
for _, permission := range data.Permissions {

134
object/invitation.go Normal file
View File

@ -0,0 +1,134 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
)
type Invitation struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
UpdatedTime string `xorm:"varchar(100)" json:"updatedTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Code string `xorm:"varchar(100)" json:"code"`
Quota int `json:"quota"`
UsedCount int `json:"usedCount"`
Application string `xorm:"varchar(100)" json:"application"`
Username string `xorm:"varchar(100)" json:"username"`
Email string `xorm:"varchar(100)" json:"email"`
Phone string `xorm:"varchar(100)" json:"phone"`
SignupGroup string `xorm:"varchar(100)" json:"signupGroup"`
State string `xorm:"varchar(100)" json:"state"`
}
func GetInvitationCount(owner, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Invitation{})
}
func GetInvitations(owner string) ([]*Invitation, error) {
invitations := []*Invitation{}
err := ormer.Engine.Desc("created_time").Find(&invitations, &Invitation{Owner: owner})
if err != nil {
return invitations, err
}
return invitations, nil
}
func GetPaginationInvitations(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Invitation, error) {
invitations := []*Invitation{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
err := session.Find(&invitations)
if err != nil {
return invitations, err
}
return invitations, nil
}
func getInvitation(owner string, name string) (*Invitation, error) {
if owner == "" || name == "" {
return nil, nil
}
invitation := Invitation{Owner: owner, Name: name}
existed, err := ormer.Engine.Get(&invitation)
if err != nil {
return &invitation, nil
}
if existed {
return &invitation, nil
} else {
return nil, nil
}
}
func GetInvitation(id string) (*Invitation, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return getInvitation(owner, name)
}
func UpdateInvitation(id string, invitation *Invitation) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
if p, err := getInvitation(owner, name); err != nil {
return false, err
} else if p == nil {
return false, nil
}
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(invitation)
if err != nil {
return false, err
}
return affected != 0, nil
}
func AddInvitation(invitation *Invitation) (bool, error) {
affected, err := ormer.Engine.Insert(invitation)
if err != nil {
return false, err
}
return affected != 0, nil
}
func DeleteInvitation(invitation *Invitation) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{invitation.Owner, invitation.Name}).Delete(&Invitation{})
if err != nil {
return false, err
}
return affected != 0, nil
}
func (invitation *Invitation) GetId() string {
return fmt.Sprintf("%s/%s", invitation.Owner, invitation.Name)
}
func VerifyInvitation(id string) (payment *Payment, attachInfo map[string]interface{}, err error) {
return nil, nil, fmt.Errorf("the invitation: %s does not exist", id)
}

View File

@ -19,6 +19,7 @@ import (
"fmt"
"strings"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
"github.com/thanhpk/randstr"
@ -356,7 +357,8 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
func GetExistUuids(owner string, uuids []string) ([]string, error) {
var existUuids []string
err := ormer.Engine.Table("user").Where("owner = ?", owner).Cols("ldap").
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
err := ormer.Engine.Table(tableNamePrefix+"user").Where("owner = ?", owner).Cols("ldap").
In("ldap", uuids).Select("DISTINCT ldap").Find(&existUuids)
if err != nil {
return existUuids, err

View File

@ -36,16 +36,14 @@ import (
)
var (
ormer *Ormer = nil
isCreateDatabaseDefined = false
createDatabase = true
ormer *Ormer = nil
createDatabase = true
configPath = "conf/app.conf"
)
func InitFlag() {
if !isCreateDatabaseDefined {
isCreateDatabaseDefined = true
createDatabase = getCreateDatabaseFlag()
}
createDatabase = getCreateDatabaseFlag()
configPath = getConfigFlag()
}
func getCreateDatabaseFlag() bool {
@ -54,6 +52,12 @@ func getCreateDatabaseFlag() bool {
return *res
}
func getConfigFlag() string {
res := flag.String("config", "conf/app.conf", "set it to \"/your/path/app.conf\" if your config file is not in: \"/conf/app.conf\"")
flag.Parse()
return *res
}
func InitConfig() {
err := beego.LoadAppConfig("ini", "../conf/app.conf")
if err != nil {
@ -68,7 +72,7 @@ func InitConfig() {
func InitAdapter() {
if conf.GetConfigString("driverName") == "" {
if !util.FileExist("conf/app.conf") {
if !util.FileExist(configPath) {
dir, err := os.Getwd()
if err != nil {
panic(err)
@ -234,12 +238,37 @@ func (a *Ormer) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Group))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(User))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Group))
err = a.Engine.Sync2(new(Invitation))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Application))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Provider))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Resource))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Cert))
if err != nil {
panic(err)
}
@ -269,17 +298,7 @@ func (a *Ormer) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Provider))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Application))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Resource))
err = a.Engine.Sync2(new(Session))
if err != nil {
panic(err)
}
@ -289,26 +308,6 @@ func (a *Ormer) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(VerificationRecord))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Webhook))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Syncer))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Cert))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Product))
if err != nil {
panic(err)
@ -319,6 +318,36 @@ func (a *Ormer) createTable() {
panic(err)
}
err = a.Engine.Sync2(new(Plan))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Pricing))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Subscription))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Syncer))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Webhook))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(VerificationRecord))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Ldap))
if err != nil {
panic(err)
@ -333,24 +362,4 @@ func (a *Ormer) createTable() {
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Session))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Subscription))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Plan))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Pricing))
if err != nil {
panic(err)
}
}

View File

@ -284,27 +284,31 @@ func removeGroupingPolicies(permission *Permission) error {
return nil
}
type CasbinRequest = []interface{}
func Enforce(permission *Permission, request *CasbinRequest, permissionIds ...string) (bool, error) {
func Enforce(permission *Permission, request []string, permissionIds ...string) (bool, error) {
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
if err != nil {
return false, err
}
return enforcer.Enforce(*request...)
// type transformation
interfaceRequest := util.StringToInterfaceArray(request)
return enforcer.Enforce(interfaceRequest...)
}
func BatchEnforce(permission *Permission, requests *[]CasbinRequest, permissionIds ...string) ([]bool, error) {
func BatchEnforce(permission *Permission, requests [][]string, permissionIds ...string) ([]bool, error) {
enforcer, err := getPermissionEnforcer(permission, permissionIds...)
if err != nil {
return nil, err
}
return enforcer.BatchEnforce(*requests)
// type transformation
interfaceRequests := util.StringToInterfaceArray2d(requests)
return enforcer.BatchEnforce(interfaceRequests)
}
func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([]string, error) {
func getEnforcers(userId string) ([]*casbin.Enforcer, error) {
permissions, _, err := getPermissionsAndRolesByUser(userId)
if err != nil {
return nil, err
@ -316,7 +320,8 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([
}
for _, role := range allRoles {
permissionsByRole, err := GetPermissionsByRole(role)
var permissionsByRole []*Permission
permissionsByRole, err = GetPermissionsByRole(role)
if err != nil {
return nil, err
}
@ -324,29 +329,45 @@ func getAllValues(userId string, fn func(enforcer *casbin.Enforcer) []string) ([
permissions = append(permissions, permissionsByRole...)
}
var values []string
var enforcers []*casbin.Enforcer
for _, permission := range permissions {
enforcer, err := getPermissionEnforcer(permission)
var enforcer *casbin.Enforcer
enforcer, err = getPermissionEnforcer(permission)
if err != nil {
return nil, err
}
values = append(values, fn(enforcer)...)
enforcers = append(enforcers, enforcer)
}
return values, nil
return enforcers, nil
}
func GetAllObjects(userId string) ([]string, error) {
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
return enforcer.GetAllObjects()
})
enforcers, err := getEnforcers(userId)
if err != nil {
return nil, err
}
res := []string{}
for _, enforcer := range enforcers {
items := enforcer.GetAllObjects()
res = append(res, items...)
}
return res, nil
}
func GetAllActions(userId string) ([]string, error) {
return getAllValues(userId, func(enforcer *casbin.Enforcer) []string {
return enforcer.GetAllActions()
})
enforcers, err := getEnforcers(userId)
if err != nil {
return nil, err
}
res := []string{}
for _, enforcer := range enforcers {
items := enforcer.GetAllObjects()
res = append(res, items...)
}
return res, nil
}
func GetAllRoles(userId string) ([]string, error) {

View File

@ -52,7 +52,7 @@ type Provider struct {
Port int `json:"port"`
DisableSsl bool `json:"disableSsl"` // If the provider type is WeChat, DisableSsl means EnableQRCode
Title string `xorm:"varchar(100)" json:"title"`
Content string `xorm:"varchar(1000)" json:"content"` // If provider type is WeChat, Content means QRCode string by Base64 encoding
Content string `xorm:"varchar(2000)" json:"content"` // If provider type is WeChat, Content means QRCode string by Base64 encoding
Receiver string `xorm:"varchar(100)" json:"receiver"`
RegionId string `xorm:"varchar(100)" json:"regionId"`

View File

@ -266,10 +266,9 @@ func (role *Role) GetId() string {
}
func getRolesByUserInternal(userId string) ([]*Role, error) {
roles := []*Role{}
user, err := GetUser(userId)
if err != nil {
return roles, err
return nil, err
}
if user == nil {
return nil, fmt.Errorf("The user: %s doesn't exist", userId)
@ -280,9 +279,10 @@ func getRolesByUserInternal(userId string) ([]*Role, error) {
query = query.Or("r.groups like ?", fmt.Sprintf("%%%s%%", group))
}
roles := []*Role{}
err = query.Find(&roles)
if err != nil {
return roles, err
return nil, err
}
res := []*Role{}
@ -291,14 +291,13 @@ func getRolesByUserInternal(userId string) ([]*Role, error) {
res = append(res, role)
}
}
return res, nil
}
func getRolesByUser(userId string) ([]*Role, error) {
roles, err := getRolesByUserInternal(userId)
if err != nil {
return roles, err
return nil, err
}
allRolesIds := []string{}
@ -379,15 +378,11 @@ func GetMaskedRoles(roles []*Role) []*Role {
// GetAncestorRoles returns a list of roles that contain the given roleIds
func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
var (
result = []*Role{}
roleMap = make(map[string]*Role)
visited = make(map[string]bool)
)
if len(roleIds) == 0 {
return result, nil
return []*Role{}, nil
}
visited := map[string]bool{}
for _, roleId := range roleIds {
visited[roleId] = true
}
@ -399,25 +394,26 @@ func GetAncestorRoles(roleIds ...string) ([]*Role, error) {
return nil, err
}
roleMap := map[string]*Role{}
for _, r := range allRoles {
roleMap[r.GetId()] = r
}
// Second, find all the roles that contain father roles
// find all the roles that contain father roles
res := []*Role{}
for _, r := range allRoles {
isContain, ok := visited[r.GetId()]
if isContain {
result = append(result, r)
res = append(res, r)
} else if !ok {
rId := r.GetId()
visited[rId] = containsRole(r, roleMap, visited, roleIds...)
if visited[rId] {
result = append(result, r)
res = append(res, r)
}
}
}
return result, nil
return res, nil
}
// containsRole is a helper function to check if a roles is related to any role in the given list roles

View File

@ -72,6 +72,10 @@ func GetTruncatedPath(provider *Provider, fullFilePath string, limit int) string
}
func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
if provider.Domain != "" && !strings.HasPrefix(provider.Domain, "http://") && !strings.HasPrefix(provider.Domain, "https://") {
provider.Domain = fmt.Sprintf("https://%s", provider.Domain)
}
escapedPath := util.UrlJoin(provider.PathPrefix, fullFilePath)
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), escapedPath)
@ -79,9 +83,6 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
if provider.Type != "Local File System" {
// provider.Domain = "https://cdn.casbin.com/casdoor/"
host = util.GetUrlHost(provider.Domain)
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
host = fmt.Sprintf("https://%s", host)
}
} else {
// provider.Domain = "http://localhost:8000" or "https://door.casdoor.com"
host = util.UrlJoin(provider.Domain, "/files")
@ -90,9 +91,12 @@ func GetUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool
host = util.UrlJoin(host, provider.Bucket)
}
fileUrl := util.UrlJoin(host, escapePath(objectKey))
fileUrl := ""
if host != "" {
fileUrl = util.UrlJoin(host, escapePath(objectKey))
}
if hasTimestamp {
if fileUrl != "" && hasTimestamp {
fileUrl = fmt.Sprintf("%s?t=%s", fileUrl, util.GetCurrentUnixTime())
}

View File

@ -54,6 +54,15 @@ func (syncer *Syncer) syncUsers() error {
var affiliationMap map[int]string
if syncer.AffiliationTable != "" {
_, affiliationMap, err = syncer.getAffiliationMap()
if err != nil {
line := fmt.Sprintf("[%s] %s\n", util.GetCurrentTime(), err.Error())
_, err2 := updateSyncerErrorText(syncer, line)
if err2 != nil {
panic(err2)
}
return err
}
}
key := syncer.getKey()

View File

@ -16,6 +16,7 @@ package object
import (
"fmt"
"reflect"
"time"
"github.com/casdoor/casdoor/util"
@ -270,6 +271,34 @@ func getClaimsWithoutThirdIdp(claims Claims) ClaimsWithoutThirdIdp {
return res
}
func getClaimsCustom(claims Claims, tokenField []string) jwt.MapClaims {
res := make(jwt.MapClaims)
userValue := reflect.ValueOf(claims.User).Elem()
res["iss"] = claims.RegisteredClaims.Issuer
res["sub"] = claims.RegisteredClaims.Subject
res["aud"] = claims.RegisteredClaims.Audience
res["exp"] = claims.RegisteredClaims.ExpiresAt
res["nbf"] = claims.RegisteredClaims.NotBefore
res["iat"] = claims.RegisteredClaims.IssuedAt
res["jti"] = claims.RegisteredClaims.ID
res["tokenType"] = claims.TokenType
res["nonce"] = claims.Nonce
res["tag"] = claims.Tag
res["scope"] = claims.Scope
for _, field := range tokenField {
userField := userValue.FieldByName(field)
if userField.IsValid() {
newfield := util.SnakeToCamel(util.CamelToSnakeCase(field))
res[newfield] = userField.Interface()
}
}
return res
}
func refineUser(user *User) *User {
user.Password = ""
@ -329,20 +358,30 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
var refreshToken *jwt.Token
// the JWT token length in "JWT-Empty" mode will be very short, as User object only has two properties: owner and name
if application.TokenFormat == "JWT-Empty" {
claimsShort := getShortClaims(claims)
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
claimsShort.TokenType = "refresh-token"
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
} else {
if application.TokenFormat == "JWT" {
claimsWithoutThirdIdp := getClaimsWithoutThirdIdp(claims)
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
claimsWithoutThirdIdp.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
claimsWithoutThirdIdp.TokenType = "refresh-token"
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsWithoutThirdIdp)
} else if application.TokenFormat == "JWT-Empty" {
claimsShort := getShortClaims(claims)
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
claimsShort.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
claimsShort.TokenType = "refresh-token"
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsShort)
} else if application.TokenFormat == "JWT-Custom" {
claimsCustom := getClaimsCustom(claims, application.TokenFields)
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsCustom)
refreshClaims := getClaimsCustom(claims, application.TokenFields)
refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime)
refreshClaims["TokenType"] = "refresh-token"
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, refreshClaims)
} else {
return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat)
}
cert, err := getCertByApplication(application)

View File

@ -867,7 +867,8 @@ func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
}
if strings.Contains(scope, "email") {
resp.Email = user.Email
resp.EmailVerified = user.EmailVerified
// resp.EmailVerified = user.EmailVerified
resp.EmailVerified = true
}
if strings.Contains(scope, "address") {
resp.Address = user.Location
@ -886,6 +887,18 @@ func (user *User) GetId() string {
return fmt.Sprintf("%s/%s", user.Owner, user.Name)
}
func (user *User) GetFriendlyName() string {
if user.FirstName != "" && user.LastName != "" {
return fmt.Sprintf("%s, %s", user.FirstName, user.LastName)
} else if user.DisplayName != "" {
return user.DisplayName
} else if user.Name != "" {
return user.Name
} else {
return user.Id
}
}
func isUserIdGlobalAdmin(userId string) bool {
return strings.HasPrefix(userId, "built-in/") || strings.HasPrefix(userId, "app/")
}

View File

@ -89,7 +89,10 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
}
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := fmt.Sprintf(provider.Content, code)
content := strings.Replace(provider.Content, "%s", code, 1)
if user != nil {
content = strings.Replace(content, "%{user.friendlyName}", user.GetFriendlyName(), 1)
}
if err := IsAllowSend(user, remoteAddr, provider.Category); err != nil {
return err

View File

@ -24,16 +24,18 @@ import (
)
const (
headerOrigin = "Origin"
headerAllowOrigin = "Access-Control-Allow-Origin"
headerAllowMethods = "Access-Control-Allow-Methods"
headerAllowHeaders = "Access-Control-Allow-Headers"
headerOrigin = "Origin"
headerAllowOrigin = "Access-Control-Allow-Origin"
headerAllowMethods = "Access-Control-Allow-Methods"
headerAllowHeaders = "Access-Control-Allow-Headers"
headerAllowCredentials = "Access-Control-Allow-Credentials"
)
func setCorsHeaders(ctx *context.Context, origin string) {
ctx.Output.Header(headerAllowOrigin, origin)
ctx.Output.Header(headerAllowMethods, "POST, GET, OPTIONS, DELETE")
ctx.Output.Header(headerAllowHeaders, "Content-Type, Authorization")
ctx.Output.Header(headerAllowCredentials, "true")
if ctx.Input.Method() == "OPTIONS" {
ctx.ResponseWriter.WriteHeader(http.StatusOK)

View File

@ -73,6 +73,12 @@ func initAPI() {
beego.Router("/api/get-default-application", &controllers.ApiController{}, "GET:GetDefaultApplication")
beego.Router("/api/get-organization-names", &controllers.ApiController{}, "GET:GetOrganizationNames")
beego.Router("/api/get-groups", &controllers.ApiController{}, "GET:GetGroups")
beego.Router("/api/get-group", &controllers.ApiController{}, "GET:GetGroup")
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
beego.Router("/api/get-global-users", &controllers.ApiController{}, "GET:GetGlobalUsers")
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
beego.Router("/api/get-sorted-users", &controllers.ApiController{}, "GET:GetSortedUsers")
@ -85,11 +91,41 @@ func initAPI() {
beego.Router("/api/upload-users", &controllers.ApiController{}, "POST:UploadUsers")
beego.Router("/api/remove-user-from-group", &controllers.ApiController{}, "POST:RemoveUserFromGroup")
beego.Router("/api/get-groups", &controllers.ApiController{}, "GET:GetGroups")
beego.Router("/api/get-group", &controllers.ApiController{}, "GET:GetGroup")
beego.Router("/api/update-group", &controllers.ApiController{}, "POST:UpdateGroup")
beego.Router("/api/add-group", &controllers.ApiController{}, "POST:AddGroup")
beego.Router("/api/delete-group", &controllers.ApiController{}, "POST:DeleteGroup")
beego.Router("/api/get-invitations", &controllers.ApiController{}, "GET:GetInvitations")
beego.Router("/api/get-invitation", &controllers.ApiController{}, "GET:GetInvitation")
beego.Router("/api/update-invitation", &controllers.ApiController{}, "POST:UpdateInvitation")
beego.Router("/api/add-invitation", &controllers.ApiController{}, "POST:AddInvitation")
beego.Router("/api/delete-invitation", &controllers.ApiController{}, "POST:DeleteInvitation")
beego.Router("/api/verify-invitation", &controllers.ApiController{}, "GET:VerifyInvitation")
beego.Router("/api/get-applications", &controllers.ApiController{}, "GET:GetApplications")
beego.Router("/api/get-application", &controllers.ApiController{}, "GET:GetApplication")
beego.Router("/api/get-user-application", &controllers.ApiController{}, "GET:GetUserApplication")
beego.Router("/api/get-organization-applications", &controllers.ApiController{}, "GET:GetOrganizationApplications")
beego.Router("/api/update-application", &controllers.ApiController{}, "POST:UpdateApplication")
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication")
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
beego.Router("/api/get-global-providers", &controllers.ApiController{}, "GET:GetGlobalProviders")
beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider")
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider")
beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider")
beego.Router("/api/get-resources", &controllers.ApiController{}, "GET:GetResources")
beego.Router("/api/get-resource", &controllers.ApiController{}, "GET:GetResource")
beego.Router("/api/update-resource", &controllers.ApiController{}, "POST:UpdateResource")
beego.Router("/api/add-resource", &controllers.ApiController{}, "POST:AddResource")
beego.Router("/api/delete-resource", &controllers.ApiController{}, "POST:DeleteResource")
beego.Router("/api/upload-resource", &controllers.ApiController{}, "POST:UploadResource")
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
beego.Router("/api/get-global-certs", &controllers.ApiController{}, "GET:GetGlobalCerts")
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
beego.Router("/api/get-roles", &controllers.ApiController{}, "GET:GetRoles")
beego.Router("/api/get-role", &controllers.ApiController{}, "GET:GetRole")
@ -107,12 +143,6 @@ func initAPI() {
beego.Router("/api/delete-permission", &controllers.ApiController{}, "POST:DeletePermission")
beego.Router("/api/upload-permissions", &controllers.ApiController{}, "POST:UploadPermissions")
beego.Router("/api/enforce", &controllers.ApiController{}, "POST:Enforce")
beego.Router("/api/batch-enforce", &controllers.ApiController{}, "POST:BatchEnforce")
beego.Router("/api/get-all-objects", &controllers.ApiController{}, "GET:GetAllObjects")
beego.Router("/api/get-all-actions", &controllers.ApiController{}, "GET:GetAllActions")
beego.Router("/api/get-all-roles", &controllers.ApiController{}, "GET:GetAllRoles")
beego.Router("/api/get-models", &controllers.ApiController{}, "GET:GetModels")
beego.Router("/api/get-model", &controllers.ApiController{}, "GET:GetModel")
beego.Router("/api/update-model", &controllers.ApiController{}, "POST:UpdateModel")
@ -135,53 +165,11 @@ func initAPI() {
beego.Router("/api/add-enforcer", &controllers.ApiController{}, "POST:AddEnforcer")
beego.Router("/api/delete-enforcer", &controllers.ApiController{}, "POST:DeleteEnforcer")
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "GET:GetEmailAndPhone")
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
beego.Router("/api/verify-code", &controllers.ApiController{}, "POST:VerifyCode")
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
beego.Router("/api/get-ldap-users", &controllers.ApiController{}, "GET:GetLdapUsers")
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "GET:GetLdaps")
beego.Router("/api/get-ldap", &controllers.ApiController{}, "GET:GetLdap")
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
beego.Router("/api/sync-ldap-users", &controllers.ApiController{}, "POST:SyncLdapUsers")
beego.Router("/api/get-providers", &controllers.ApiController{}, "GET:GetProviders")
beego.Router("/api/get-provider", &controllers.ApiController{}, "GET:GetProvider")
beego.Router("/api/get-global-providers", &controllers.ApiController{}, "GET:GetGlobalProviders")
beego.Router("/api/update-provider", &controllers.ApiController{}, "POST:UpdateProvider")
beego.Router("/api/add-provider", &controllers.ApiController{}, "POST:AddProvider")
beego.Router("/api/delete-provider", &controllers.ApiController{}, "POST:DeleteProvider")
beego.Router("/api/get-applications", &controllers.ApiController{}, "GET:GetApplications")
beego.Router("/api/get-application", &controllers.ApiController{}, "GET:GetApplication")
beego.Router("/api/get-user-application", &controllers.ApiController{}, "GET:GetUserApplication")
beego.Router("/api/get-organization-applications", &controllers.ApiController{}, "GET:GetOrganizationApplications")
beego.Router("/api/update-application", &controllers.ApiController{}, "POST:UpdateApplication")
beego.Router("/api/add-application", &controllers.ApiController{}, "POST:AddApplication")
beego.Router("/api/delete-application", &controllers.ApiController{}, "POST:DeleteApplication")
beego.Router("/api/get-resources", &controllers.ApiController{}, "GET:GetResources")
beego.Router("/api/get-resource", &controllers.ApiController{}, "GET:GetResource")
beego.Router("/api/update-resource", &controllers.ApiController{}, "POST:UpdateResource")
beego.Router("/api/add-resource", &controllers.ApiController{}, "POST:AddResource")
beego.Router("/api/delete-resource", &controllers.ApiController{}, "POST:DeleteResource")
beego.Router("/api/upload-resource", &controllers.ApiController{}, "POST:UploadResource")
beego.Router("/api/get-tokens", &controllers.ApiController{}, "GET:GetTokens")
beego.Router("/api/get-token", &controllers.ApiController{}, "GET:GetToken")
beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken")
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
beego.Router("/api/enforce", &controllers.ApiController{}, "POST:Enforce")
beego.Router("/api/batch-enforce", &controllers.ApiController{}, "POST:BatchEnforce")
beego.Router("/api/get-all-objects", &controllers.ApiController{}, "GET:GetAllObjects")
beego.Router("/api/get-all-actions", &controllers.ApiController{}, "GET:GetAllActions")
beego.Router("/api/get-all-roles", &controllers.ApiController{}, "GET:GetAllRoles")
beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions")
beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession")
@ -190,43 +178,11 @@ func initAPI() {
beego.Router("/api/delete-session", &controllers.ApiController{}, "POST:DeleteSession")
beego.Router("/api/is-session-duplicated", &controllers.ApiController{}, "GET:IsSessionDuplicated")
beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")
beego.Router("/api/update-webhook", &controllers.ApiController{}, "POST:UpdateWebhook")
beego.Router("/api/add-webhook", &controllers.ApiController{}, "POST:AddWebhook")
beego.Router("/api/delete-webhook", &controllers.ApiController{}, "POST:DeleteWebhook")
beego.Router("/api/get-syncers", &controllers.ApiController{}, "GET:GetSyncers")
beego.Router("/api/get-syncer", &controllers.ApiController{}, "GET:GetSyncer")
beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer")
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
beego.Router("/api/get-global-certs", &controllers.ApiController{}, "GET:GetGlobalCerts")
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")
beego.Router("/api/get-subscriptions", &controllers.ApiController{}, "GET:GetSubscriptions")
beego.Router("/api/get-subscription", &controllers.ApiController{}, "GET:GetSubscription")
beego.Router("/api/update-subscription", &controllers.ApiController{}, "POST:UpdateSubscription")
beego.Router("/api/add-subscription", &controllers.ApiController{}, "POST:AddSubscription")
beego.Router("/api/delete-subscription", &controllers.ApiController{}, "POST:DeleteSubscription")
beego.Router("/api/get-plans", &controllers.ApiController{}, "GET:GetPlans")
beego.Router("/api/get-plan", &controllers.ApiController{}, "GET:GetPlan")
beego.Router("/api/update-plan", &controllers.ApiController{}, "POST:UpdatePlan")
beego.Router("/api/add-plan", &controllers.ApiController{}, "POST:AddPlan")
beego.Router("/api/delete-plan", &controllers.ApiController{}, "POST:DeletePlan")
beego.Router("/api/get-pricings", &controllers.ApiController{}, "GET:GetPricings")
beego.Router("/api/get-pricing", &controllers.ApiController{}, "GET:GetPricing")
beego.Router("/api/update-pricing", &controllers.ApiController{}, "POST:UpdatePricing")
beego.Router("/api/add-pricing", &controllers.ApiController{}, "POST:AddPricing")
beego.Router("/api/delete-pricing", &controllers.ApiController{}, "POST:DeletePricing")
beego.Router("/api/get-tokens", &controllers.ApiController{}, "GET:GetTokens")
beego.Router("/api/get-token", &controllers.ApiController{}, "GET:GetToken")
beego.Router("/api/update-token", &controllers.ApiController{}, "POST:UpdateToken")
beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken")
beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken")
beego.Router("/api/get-products", &controllers.ApiController{}, "GET:GetProducts")
beego.Router("/api/get-product", &controllers.ApiController{}, "GET:GetProduct")
@ -244,6 +200,64 @@ func initAPI() {
beego.Router("/api/notify-payment/?:owner/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
beego.Router("/api/invoice-payment", &controllers.ApiController{}, "POST:InvoicePayment")
beego.Router("/api/get-plans", &controllers.ApiController{}, "GET:GetPlans")
beego.Router("/api/get-plan", &controllers.ApiController{}, "GET:GetPlan")
beego.Router("/api/update-plan", &controllers.ApiController{}, "POST:UpdatePlan")
beego.Router("/api/add-plan", &controllers.ApiController{}, "POST:AddPlan")
beego.Router("/api/delete-plan", &controllers.ApiController{}, "POST:DeletePlan")
beego.Router("/api/get-pricings", &controllers.ApiController{}, "GET:GetPricings")
beego.Router("/api/get-pricing", &controllers.ApiController{}, "GET:GetPricing")
beego.Router("/api/update-pricing", &controllers.ApiController{}, "POST:UpdatePricing")
beego.Router("/api/add-pricing", &controllers.ApiController{}, "POST:AddPricing")
beego.Router("/api/delete-pricing", &controllers.ApiController{}, "POST:DeletePricing")
beego.Router("/api/get-subscriptions", &controllers.ApiController{}, "GET:GetSubscriptions")
beego.Router("/api/get-subscription", &controllers.ApiController{}, "GET:GetSubscription")
beego.Router("/api/update-subscription", &controllers.ApiController{}, "POST:UpdateSubscription")
beego.Router("/api/add-subscription", &controllers.ApiController{}, "POST:AddSubscription")
beego.Router("/api/delete-subscription", &controllers.ApiController{}, "POST:DeleteSubscription")
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
beego.Router("/api/health", &controllers.ApiController{}, "GET:Health")
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
beego.Handler("/api/metrics", promhttp.Handler())
beego.Router("/api/get-syncers", &controllers.ApiController{}, "GET:GetSyncers")
beego.Router("/api/get-syncer", &controllers.ApiController{}, "GET:GetSyncer")
beego.Router("/api/update-syncer", &controllers.ApiController{}, "POST:UpdateSyncer")
beego.Router("/api/add-syncer", &controllers.ApiController{}, "POST:AddSyncer")
beego.Router("/api/delete-syncer", &controllers.ApiController{}, "POST:DeleteSyncer")
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
beego.Router("/api/get-webhooks", &controllers.ApiController{}, "GET:GetWebhooks")
beego.Router("/api/get-webhook", &controllers.ApiController{}, "GET:GetWebhook")
beego.Router("/api/update-webhook", &controllers.ApiController{}, "POST:UpdateWebhook")
beego.Router("/api/add-webhook", &controllers.ApiController{}, "POST:AddWebhook")
beego.Router("/api/delete-webhook", &controllers.ApiController{}, "POST:DeleteWebhook")
beego.Router("/api/set-password", &controllers.ApiController{}, "POST:SetPassword")
beego.Router("/api/check-user-password", &controllers.ApiController{}, "POST:CheckUserPassword")
beego.Router("/api/get-email-and-phone", &controllers.ApiController{}, "GET:GetEmailAndPhone")
beego.Router("/api/send-verification-code", &controllers.ApiController{}, "POST:SendVerificationCode")
beego.Router("/api/verify-code", &controllers.ApiController{}, "POST:VerifyCode")
beego.Router("/api/verify-captcha", &controllers.ApiController{}, "POST:VerifyCaptcha")
beego.Router("/api/reset-email-or-phone", &controllers.ApiController{}, "POST:ResetEmailOrPhone")
beego.Router("/api/get-captcha", &controllers.ApiController{}, "GET:GetCaptcha")
beego.Router("/api/get-ldap-users", &controllers.ApiController{}, "GET:GetLdapUsers")
beego.Router("/api/get-ldaps", &controllers.ApiController{}, "GET:GetLdaps")
beego.Router("/api/get-ldap", &controllers.ApiController{}, "GET:GetLdap")
beego.Router("/api/add-ldap", &controllers.ApiController{}, "POST:AddLdap")
beego.Router("/api/update-ldap", &controllers.ApiController{}, "POST:UpdateLdap")
beego.Router("/api/delete-ldap", &controllers.ApiController{}, "POST:DeleteLdap")
beego.Router("/api/sync-ldap-users", &controllers.ApiController{}, "POST:SyncLdapUsers")
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
beego.Router("/api/send-notification", &controllers.ApiController{}, "POST:SendNotification")
@ -259,13 +273,6 @@ func initAPI() {
beego.Router("/api/delete-mfa", &controllers.ApiController{}, "POST:DeleteMfa")
beego.Router("/api/set-preferred-mfa", &controllers.ApiController{}, "POST:SetPreferredMfa")
beego.Router("/api/get-system-info", &controllers.ApiController{}, "GET:GetSystemInfo")
beego.Router("/api/get-version-info", &controllers.ApiController{}, "GET:GetVersionInfo")
beego.Router("/api/health", &controllers.ApiController{}, "GET:Health")
beego.Router("/api/get-prometheus-info", &controllers.ApiController{}, "GET:GetPrometheusInfo")
beego.Handler("/api/metrics", promhttp.Handler())
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")

View File

@ -34,6 +34,8 @@ func GetStorageProvider(providerType string, clientId string, clientSecret strin
return NewQiniuCloudKodoStorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "Google Cloud Storage":
return NewGoogleCloudStorageProvider(clientSecret, bucket, endpoint)
case "Synology":
return NewSynologyNasStorageProvider(clientId, clientSecret, endpoint)
}
return nil

31
storage/synology_nas.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package storage
import (
"github.com/casdoor/oss"
"github.com/casdoor/oss/synology"
)
func NewSynologyNasStorageProvider(clientId string, clientSecret string, endpoint string) oss.StorageInterface {
sp := synology.New(&synology.Config{
AccessID: clientId,
AccessKey: clientSecret,
Endpoint: endpoint,
SharedFolder: "/home",
})
return sp
}

View File

@ -811,9 +811,9 @@
],
"responses": {
"200": {
"description": "object",
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -839,9 +839,9 @@
],
"responses": {
"200": {
"description": "object",
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -881,9 +881,9 @@
],
"responses": {
"200": {
"description": "object",
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -935,7 +935,10 @@
"description": "array of casbin requests",
"required": true,
"schema": {
"$ref": "#/definitions/object.CasbinRequest"
"type": "array",
"items": {
"type": "string"
}
}
},
{
@ -1108,7 +1111,7 @@
"description": "The enforcer object",
"required": true,
"schema": {
"$ref": "#/definitions/object.Enforce"
"$ref": "#/definitions/object.Enforcer"
}
}
],
@ -1187,9 +1190,9 @@
"operationId": "ApiController.DeleteMfa",
"responses": {
"200": {
"description": "object",
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -1657,7 +1660,10 @@
"description": "Casbin request",
"required": true,
"schema": {
"$ref": "#/definitions/object.CasbinRequest"
"type": "array",
"items": {
"type": "string"
}
}
},
{
@ -1809,7 +1815,7 @@
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -1962,7 +1968,7 @@
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -2208,7 +2214,7 @@
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/LdapResp"
"$ref": "#/definitions/controllers.LdapResp"
}
}
}
@ -3826,9 +3832,9 @@
"operationId": "ApiController.MfaSetupEnable",
"responses": {
"200": {
"description": "object",
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -3860,9 +3866,9 @@
"operationId": "ApiController.MfaSetupVerify",
"responses": {
"200": {
"description": "object",
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -3996,9 +4002,9 @@
"operationId": "ApiController.SetPreferredMfa",
"responses": {
"200": {
"description": "object",
"description": "The Response object",
"schema": {
"$ref": "#/definitions/Response"
"$ref": "#/definitions/controllers.Response"
}
}
}
@ -4057,7 +4063,7 @@
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/LdapSyncResp"
"$ref": "#/definitions/controllers.LdapSyncResp"
}
}
}
@ -4925,7 +4931,7 @@
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/LaravelResponse"
"$ref": "#/definitions/controllers.LaravelResponse"
}
}
}
@ -5131,22 +5137,6 @@
}
},
"definitions": {
"LaravelResponse": {
"title": "LaravelResponse",
"type": "object"
},
"LdapResp": {
"title": "LdapResp",
"type": "object"
},
"LdapSyncResp": {
"title": "LdapSyncResp",
"type": "object"
},
"Response": {
"title": "Response",
"type": "object"
},
"casbin.Enforcer": {
"title": "Enforcer",
"type": "object"
@ -5179,6 +5169,66 @@
}
}
},
"controllers.LaravelResponse": {
"title": "LaravelResponse",
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"email": {
"type": "string"
},
"email_verified_at": {
"type": "string"
},
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"controllers.LdapResp": {
"title": "LdapResp",
"type": "object",
"properties": {
"existUuids": {
"type": "array",
"items": {
"type": "string"
}
},
"users": {
"type": "array",
"items": {
"$ref": "#/definitions/object.LdapUser"
}
}
}
},
"controllers.LdapSyncResp": {
"title": "LdapSyncResp",
"type": "object",
"properties": {
"exist": {
"type": "array",
"items": {
"$ref": "#/definitions/object.LdapUser"
}
},
"failed": {
"type": "array",
"items": {
"$ref": "#/definitions/object.LdapUser"
}
}
}
},
"controllers.NotificationForm": {
"title": "NotificationForm",
"type": "object",
@ -5236,10 +5286,6 @@
}
}
},
"jose.JSONWebKey": {
"title": "JSONWebKey",
"type": "object"
},
"model.Model": {
"title": "Model",
"type": "object"
@ -5368,6 +5414,14 @@
"type": "integer",
"format": "int64"
},
"failedSigninLimit": {
"type": "integer",
"format": "int64"
},
"failedSigninFrozenTime": {
"type": "integer",
"format": "int64"
},
"forgetUrl": {
"type": "string"
},
@ -5480,12 +5534,6 @@
}
}
},
"object.CasbinRequest": {
"title": "CasbinRequest",
"type": "array",
"items": {
}
},
"object.Cert": {
"title": "Cert",
"type": "object",
@ -5527,10 +5575,6 @@
}
}
},
"object.Enforce": {
"title": "Enforce",
"type": "object"
},
"object.Enforcer": {
"title": "Enforcer",
"type": "object",
@ -5771,6 +5815,66 @@
}
}
},
"object.LdapUser": {
"title": "LdapUser",
"type": "object",
"properties": {
"EmailAddress": {
"type": "string"
},
"Mail": {
"type": "string"
},
"MobileTelephoneNumber": {
"type": "string"
},
"PostalAddress": {
"type": "string"
},
"RegisteredAddress": {
"type": "string"
},
"TelephoneNumber": {
"type": "string"
},
"address": {
"type": "string"
},
"cn": {
"type": "string"
},
"displayName": {
"type": "string"
},
"email": {
"type": "string"
},
"gidNumber": {
"type": "string"
},
"groupId": {
"type": "string"
},
"memberOf": {
"type": "string"
},
"mobile": {
"type": "string"
},
"uid": {
"type": "string"
},
"uidNumber": {
"type": "string"
},
"userPrincipalName": {
"type": "string"
},
"uuid": {
"type": "string"
}
}
},
"object.ManagedAccount": {
"title": "ManagedAccount",
"type": "object",
@ -6657,7 +6761,7 @@
"name": {
"type": "string"
},
"nameformat": {
"nameFormat": {
"type": "string"
},
"value": {
@ -7529,22 +7633,6 @@
],
"example": "Paid"
},
"protocol.CredentialAssertion": {
"title": "CredentialAssertion",
"type": "object"
},
"protocol.CredentialAssertionResponse": {
"title": "CredentialAssertionResponse",
"type": "object"
},
"protocol.CredentialCreation": {
"title": "CredentialCreation",
"type": "object"
},
"protocol.CredentialCreationResponse": {
"title": "CredentialCreationResponse",
"type": "object"
},
"util.SystemInfo": {
"title": "SystemInfo",
"type": "object",

View File

@ -525,9 +525,9 @@ paths:
$ref: '#/definitions/controllers.EmailForm'
responses:
"200":
description: object
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/api/send-notification:
post:
tags:
@ -543,9 +543,9 @@ paths:
$ref: '#/definitions/controllers.NotificationForm'
responses:
"200":
description: object
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/api/send-sms:
post:
tags:
@ -571,9 +571,9 @@ paths:
$ref: '#/definitions/controllers.SmsForm'
responses:
"200":
description: object
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/api/verify-code:
post:
tags:
@ -606,7 +606,9 @@ paths:
description: array of casbin requests
required: true
schema:
$ref: '#/definitions/object.CasbinRequest'
type: array
items:
type: string
- in: query
name: permissionId
description: permission id
@ -718,7 +720,7 @@ paths:
description: The enforcer object
required: true
schema:
$ref: '#/definitions/object.Enforce'
$ref: '#/definitions/object.Enforcer'
responses:
"200":
description: ""
@ -768,9 +770,9 @@ paths:
operationId: ApiController.DeleteMfa
responses:
"200":
description: object
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/delete-model:
post:
tags:
@ -1071,7 +1073,9 @@ paths:
description: Casbin request
required: true
schema:
$ref: '#/definitions/object.CasbinRequest'
type: array
items:
type: string
- in: query
name: permissionId
description: permission id
@ -1172,7 +1176,7 @@ paths:
"200":
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/get-application:
get:
tags:
@ -1272,7 +1276,7 @@ paths:
"200":
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/get-email-and-phone:
get:
tags:
@ -1433,7 +1437,7 @@ paths:
"200":
description: The Response object
schema:
$ref: '#/definitions/LdapResp'
$ref: '#/definitions/controllers.LdapResp'
/api/get-ldaps:
get:
tags:
@ -2496,9 +2500,9 @@ paths:
operationId: ApiController.MfaSetupEnable
responses:
"200":
description: object
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/mfa/setup/initiate:
post:
tags:
@ -2518,9 +2522,9 @@ paths:
operationId: ApiController.MfaSetupVerify
responses:
"200":
description: object
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/notify-payment:
post:
tags:
@ -2607,9 +2611,9 @@ paths:
operationId: ApiController.SetPreferredMfa
responses:
"200":
description: object
description: The Response object
schema:
$ref: '#/definitions/Response'
$ref: '#/definitions/controllers.Response'
/api/signup:
post:
tags:
@ -2648,7 +2652,7 @@ paths:
"200":
description: The Response object
schema:
$ref: '#/definitions/LdapSyncResp'
$ref: '#/definitions/controllers.LdapSyncResp'
/api/unlink:
post:
tags:
@ -3219,7 +3223,7 @@ paths:
"200":
description: The Response object
schema:
$ref: '#/definitions/LaravelResponse'
$ref: '#/definitions/controllers.LaravelResponse'
/api/userinfo:
get:
tags:
@ -3351,18 +3355,6 @@ paths:
schema:
$ref: '#/definitions/object.TokenError'
definitions:
LaravelResponse:
title: LaravelResponse
type: object
LdapResp:
title: LdapResp
type: object
LdapSyncResp:
title: LdapSyncResp
type: object
Response:
title: Response
type: object
casbin.Enforcer:
title: Enforcer
type: object
@ -3385,6 +3377,46 @@ definitions:
type: string
title:
type: string
controllers.LaravelResponse:
title: LaravelResponse
type: object
properties:
created_at:
type: string
email:
type: string
email_verified_at:
type: string
id:
type: string
name:
type: string
updated_at:
type: string
controllers.LdapResp:
title: LdapResp
type: object
properties:
existUuids:
type: array
items:
type: string
users:
type: array
items:
$ref: '#/definitions/object.LdapUser'
controllers.LdapSyncResp:
title: LdapSyncResp
type: object
properties:
exist:
type: array
items:
$ref: '#/definitions/object.LdapUser'
failed:
type: array
items:
$ref: '#/definitions/object.LdapUser'
controllers.NotificationForm:
title: NotificationForm
type: object
@ -3514,6 +3546,12 @@ definitions:
expireInHours:
type: integer
format: int64
failedSigninLimit:
type: integer
format: int64
failedSigninFrozenTime:
type: integer
format: int64
forgetUrl:
type: string
formBackgroundUrl:
@ -3588,11 +3626,6 @@ definitions:
$ref: '#/definitions/object.ThemeData'
tokenFormat:
type: string
object.CasbinRequest:
title: CasbinRequest
type: array
items:
$ref: '#/definitions/object.CasbinRequest'
object.Cert:
title: Cert
type: object
@ -3621,9 +3654,6 @@ definitions:
type: string
type:
type: string
object.Enforce:
title: Enforce
type: object
object.Enforcer:
title: Enforcer
type: object
@ -3786,6 +3816,46 @@ definitions:
type: string
username:
type: string
object.LdapUser:
title: LdapUser
type: object
properties:
EmailAddress:
type: string
Mail:
type: string
MobileTelephoneNumber:
type: string
PostalAddress:
type: string
RegisteredAddress:
type: string
TelephoneNumber:
type: string
address:
type: string
cn:
type: string
displayName:
type: string
email:
type: string
gidNumber:
type: string
groupId:
type: string
memberOf:
type: string
mobile:
type: string
uid:
type: string
uidNumber:
type: string
userPrincipalName:
type: string
uuid:
type: string
object.ManagedAccount:
title: ManagedAccount
type: object
@ -4381,7 +4451,7 @@ definitions:
properties:
name:
type: string
nameformat:
nameFormat:
type: string
value:
type: string

View File

@ -72,7 +72,15 @@ func GetUrlPath(urlString string) string {
}
func GetUrlHost(urlString string) string {
u, _ := url.Parse(urlString)
if urlString == "" {
return ""
}
u, err := url.Parse(urlString)
if err != nil {
return err.Error()
}
return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
}

View File

@ -322,3 +322,23 @@ func GetUsernameFromEmail(email string) string {
return tokens[0]
}
}
func StringToInterfaceArray(array []string) []interface{} {
var interfaceArray []interface{}
for _, v := range array {
interfaceArray = append(interfaceArray, v)
}
return interfaceArray
}
func StringToInterfaceArray2d(arrays [][]string) [][]interface{} {
var interfaceArrays [][]interface{}
for _, req := range arrays {
var interfaceArray []interface{}
for _, r := range req {
interfaceArray = append(interfaceArray, r)
}
interfaceArrays = append(interfaceArrays, interfaceArray)
}
return interfaceArrays
}

View File

@ -15,62 +15,64 @@
import React, {Component} from "react";
import "./App.less";
import {Helmet} from "react-helmet";
import Dashboard from "./basic/Dashboard";
import ShortcutsPage from "./basic/ShortcutsPage";
import * as Setting from "./Setting";
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
import {AppstoreTwoTone, BarsOutlined, DeploymentUnitOutlined, DollarTwoTone, DownOutlined, GithubOutlined, HomeTwoTone, InfoCircleFilled, LockTwoTone, LogoutOutlined, SafetyCertificateTwoTone, SettingOutlined, SettingTwoTone, ShareAltOutlined, WalletTwoTone} from "@ant-design/icons";
import {Alert, Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result, Tooltip} from "antd";
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import AccountPage from "./account/AccountPage";
import Dashboard from "./basic/Dashboard";
import ShortcutsPage from "./basic/ShortcutsPage";
import AppListPage from "./basic/AppListPage";
import OrganizationListPage from "./OrganizationListPage";
import OrganizationEditPage from "./OrganizationEditPage";
import GroupEditPage from "./GroupEdit";
import GroupListPage from "./GroupList";
import GroupTreePage from "./GroupTreePage";
import UserListPage from "./UserListPage";
import UserEditPage from "./UserEditPage";
import InvitationListPage from "./InvitationListPage";
import InvitationEditPage from "./InvitationEditPage";
import ApplicationListPage from "./ApplicationListPage";
import ApplicationEditPage from "./ApplicationEditPage";
import ProviderListPage from "./ProviderListPage";
import ProviderEditPage from "./ProviderEditPage";
import ResourceListPage from "./ResourceListPage";
import CertListPage from "./CertListPage";
import CertEditPage from "./CertEditPage";
import RoleListPage from "./RoleListPage";
import RoleEditPage from "./RoleEditPage";
import PermissionListPage from "./PermissionListPage";
import PermissionEditPage from "./PermissionEditPage";
import ModelListPage from "./ModelListPage";
import ModelEditPage from "./ModelEditPage";
import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage";
import EnforcerEditPage from "./EnforcerEditPage";
import EnforcerListPage from "./EnforcerListPage";
import GroupTreePage from "./GroupTreePage";
import GroupEditPage from "./GroupEdit";
import GroupListPage from "./GroupList";
import ProviderListPage from "./ProviderListPage";
import ProviderEditPage from "./ProviderEditPage";
import ApplicationListPage from "./ApplicationListPage";
import ApplicationEditPage from "./ApplicationEditPage";
import ResourceListPage from "./ResourceListPage";
import LdapEditPage from "./LdapEditPage";
import LdapSyncPage from "./LdapSyncPage";
import SessionListPage from "./SessionListPage";
import TokenListPage from "./TokenListPage";
import TokenEditPage from "./TokenEditPage";
import WebhookListPage from "./WebhookListPage";
import WebhookEditPage from "./WebhookEditPage";
import SyncerListPage from "./SyncerListPage";
import SyncerEditPage from "./SyncerEditPage";
import CertListPage from "./CertListPage";
import CertEditPage from "./CertEditPage";
import SubscriptionListPage from "./SubscriptionListPage";
import SubscriptionEditPage from "./SubscriptionEditPage";
import PricingListPage from "./PricingListPage";
import PricingEditPage from "./PricingEditPage";
import PlanListPage from "./PlanListPage";
import PlanEditPage from "./PlanEditPage";
import ProductListPage from "./ProductListPage";
import ProductEditPage from "./ProductEditPage";
import ProductBuyPage from "./ProductBuyPage";
import PaymentListPage from "./PaymentListPage";
import PaymentEditPage from "./PaymentEditPage";
import PaymentResultPage from "./PaymentResultPage";
import ModelListPage from "./ModelListPage";
import ModelEditPage from "./ModelEditPage";
import AdapterListPage from "./AdapterListPage";
import AdapterEditPage from "./AdapterEditPage";
import SessionListPage from "./SessionListPage";
import MfaSetupPage from "./auth/MfaSetupPage";
import PricingListPage from "./PricingListPage";
import PricingEditPage from "./PricingEditPage";
import PlanListPage from "./PlanListPage";
import PlanEditPage from "./PlanEditPage";
import SubscriptionListPage from "./SubscriptionListPage";
import SubscriptionEditPage from "./SubscriptionEditPage";
import SystemInfo from "./SystemInfo";
import AccountPage from "./account/AccountPage";
import AppListPage from "./basic/AppListPage";
import SyncerListPage from "./SyncerListPage";
import SyncerEditPage from "./SyncerEditPage";
import WebhookListPage from "./WebhookListPage";
import WebhookEditPage from "./WebhookEditPage";
import LdapEditPage from "./LdapEditPage";
import LdapSyncPage from "./LdapSyncPage";
import MfaSetupPage from "./auth/MfaSetupPage";
import CustomGithubCorner from "./common/CustomGithubCorner";
import * as Conf from "./Conf";
@ -153,7 +155,7 @@ class App extends Component {
});
if (uri === "/" || uri.includes("/shortcuts") || uri.includes("/apps")) {
this.setState({selectedMenuKey: "/home"});
} else if (uri.includes("/organizations") || uri.includes("/trees") || uri.includes("/users") || uri.includes("/groups")) {
} else if (uri.includes("/organizations") || uri.includes("/trees") || uri.includes("/groups") || uri.includes("/users") || uri.includes("/invitations")) {
this.setState({selectedMenuKey: "/orgs"});
} else if (uri.includes("/applications") || uri.includes("/providers") || uri.includes("/resources") || uri.includes("/certs")) {
this.setState({selectedMenuKey: "/identity"});
@ -390,7 +392,7 @@ class App extends Component {
</div>
</Tooltip>
<OpenTour />
{Setting.isAdminUser(this.state.account) && !Setting.isMobile() &&
{Setting.isAdminUser(this.state.account) && !Setting.isMobile() && (this.state.uri.indexOf("/trees") === -1) &&
<OrganizationSelect
initValue={Setting.getOrganization()}
withAll={true}
@ -434,6 +436,7 @@ class App extends Component {
Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>, "/organizations"),
Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>, "/groups"),
Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>, "/users"),
Setting.getItem(<Link to="/invitations">{i18next.t("general:Invitations")}</Link>, "/invitations"),
]));
res.push(Setting.getItem(<Link style={{color: "black"}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone />, [
@ -514,48 +517,49 @@ class App extends Component {
<Route exact path="/groups/:organizationName/:groupName" render={(props) => this.renderLoginIfNotLoggedIn(<GroupEditPage account={this.state.account} {...props} />)} />
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
<Route exact path="/invitations" render={(props) => this.renderLoginIfNotLoggedIn(<InvitationListPage account={this.state.account} {...props} />)} />
<Route exact path="/invitations/:organizationName/:invitationName" render={(props) => this.renderLoginIfNotLoggedIn(<InvitationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:organizationName/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
<Route exact path="/enforcers" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerListPage account={this.state.account} {...props} />)} />
<Route exact path="/enforcers/:organizationName/:enforcerName" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
<Route exact path="/ldap/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/sync/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
<Route exact path="/enforcers" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerListPage account={this.state.account} {...props} />)} />
<Route exact path="/enforcers/:organizationName/:enforcerName" render={(props) => this.renderLoginIfNotLoggedIn(<EnforcerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/sessions" render={(props) => this.renderLoginIfNotLoggedIn(<SessionListPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:organizationName/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
<Route exact path="/plans" render={(props) => this.renderLoginIfNotLoggedIn(<PlanListPage account={this.state.account} {...props} />)} />
<Route exact path="/plans/:organizationName/:planName" render={(props) => this.renderLoginIfNotLoggedIn(<PlanEditPage account={this.state.account} {...props} />)} />
<Route exact path="/pricings" render={(props) => this.renderLoginIfNotLoggedIn(<PricingListPage account={this.state.account} {...props} />)} />
<Route exact path="/pricings/:organizationName/:pricingName" render={(props) => this.renderLoginIfNotLoggedIn(<PricingEditPage account={this.state.account} {...props} />)} />
<Route exact path="/subscriptions" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionListPage account={this.state.account} {...props} />)} />
<Route exact path="/subscriptions/:organizationName/:subscriptionName" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:organizationName/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
<Route exact path="/products/:organizationName/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:organizationName/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
<Route exact path="/payments/:organizationName/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
<Route exact path="/plans" render={(props) => this.renderLoginIfNotLoggedIn(<PlanListPage account={this.state.account} {...props} />)} />
<Route exact path="/plans/:organizationName/:planName" render={(props) => this.renderLoginIfNotLoggedIn(<PlanEditPage account={this.state.account} {...props} />)} />
<Route exact path="/pricings" render={(props) => this.renderLoginIfNotLoggedIn(<PricingListPage account={this.state.account} {...props} />)} />
<Route exact path="/pricings/:organizationName/:pricingName" render={(props) => this.renderLoginIfNotLoggedIn(<PricingEditPage account={this.state.account} {...props} />)} />
<Route exact path="/subscriptions" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionListPage account={this.state.account} {...props} />)} />
<Route exact path="/subscriptions/:organizationName/:subscriptionName" render={(props) => this.renderLoginIfNotLoggedIn(<SubscriptionEditPage account={this.state.account} {...props} />)} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
<Route exact path="/ldap/sync/:organizationName/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
<Route exact path="/mfa/setup" render={(props) => this.renderLoginIfNotLoggedIn(<MfaSetupPage account={this.state.account} onfinish={() => this.setState({requiredEnableMfa: false})} {...props} />)} />
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
</Switch>

View File

@ -27,6 +27,7 @@ import LoginPage from "./auth/LoginPage";
import i18next from "i18next";
import UrlTable from "./table/UrlTable";
import ProviderTable from "./table/ProviderTable";
import SigninMethodTable from "./table/SigninMethodTable";
import SignupTable from "./table/SignupTable";
import SamlAttributeTable from "./table/SamlAttributeTable";
import PromptPage from "./auth/PromptPage";
@ -385,10 +386,22 @@ class ApplicationEditPage extends React.Component {
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField("tokenFormat", value);})}
options={["JWT", "JWT-Empty"].map((item) => Setting.getOption(item, item))}
options={["JWT", "JWT-Empty", "JWT-Custom"].map((item) => Setting.getOption(item, item))}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Token fields"), i18next.t("application:Token fields - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} disabled={this.state.application.tokenFormat !== "JWT-Custom"} mode="tags" showSearch style={{width: "100%"}} value={this.state.application.tokenFields} onChange={(value => {this.updateApplicationField("tokenFields", value);})}>
{
Setting.getUserCommonFields().map((item, index) => <Option key={index} value={item}>{item}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Token expire"), i18next.t("application:Token expire - Tooltip"))} :
@ -424,18 +437,8 @@ class ApplicationEditPage extends React.Component {
{Setting.getLabel(i18next.t("application:Failed signin frozen time"), i18next.t("application:Failed signin frozen time - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber style={{width: "150px"}} value={this.state.application.failedSigninfrozenTime} min={1} step={1} precision={0} addonAfter="Minutes" onChange={value => {
this.updateApplicationField("failedSigninfrozenTime", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable password"), i18next.t("application:Enable password - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enablePassword} onChange={checked => {
this.updateApplicationField("enablePassword", checked);
<InputNumber style={{width: "150px"}} value={this.state.application.failedSigninFrozenTime} min={1} step={1} precision={0} addonAfter="Minutes" onChange={value => {
this.updateApplicationField("failedSigninFrozenTime", value);
}} />
</Col>
</Row>
@ -469,26 +472,6 @@ class ApplicationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable code signin"), i18next.t("application:Enable code signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableCodeSignin} onChange={checked => {
this.updateApplicationField("enableCodeSignin", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable WebAuthn signin"), i18next.t("application:Enable WebAuthn signin - Tooltip"))} :
</Col>
<Col span={1} >
<Switch checked={this.state.application.enableWebAuthn} onChange={checked => {
this.updateApplicationField("enableWebAuthn", checked);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("application:Enable Email linking"), i18next.t("application:Enable Email linking - Tooltip"))} :
@ -499,6 +482,20 @@ class ApplicationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Signin methods"), i18next.t("application:Signin methods - Tooltip"))} :
</Col>
<Col span={22} >
<SigninMethodTable
title={i18next.t("application:Signin methods")}
table={this.state.application.signinMethods}
onUpdateTable={(value) => {
this.updateApplicationField("signinMethods", value);
}}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("application:Org choice mode"), i18next.t("application:Org choice mode - Tooltip"))} :
@ -800,7 +797,7 @@ class ApplicationEditPage extends React.Component {
</Col>
<Col span={22} >
<Row style={{marginTop: "20px"}} >
<Radio.Group onChange={e => {this.updateApplicationField("formOffset", e.target.value);}} value={this.state.application.formOffset}>
<Radio.Group buttonStyle="solid" onChange={e => {this.updateApplicationField("formOffset", e.target.value);}} value={this.state.application.formOffset}>
<Radio.Button value={1}>{i18next.t("application:Left")}</Radio.Button>
<Radio.Button value={2}>{i18next.t("application:Center")}</Radio.Button>
<Radio.Button value={3}>{i18next.t("application:Right")}</Radio.Button>
@ -840,7 +837,7 @@ class ApplicationEditPage extends React.Component {
</Col>
<Col span={22} style={{marginTop: "5px"}}>
<Row>
<Radio.Group value={this.state.application.themeData?.isEnabled ?? false} onChange={e => {
<Radio.Group buttonStyle="solid" value={this.state.application.themeData?.isEnabled ?? false} onChange={e => {
const {_, ...theme} = this.state.application.themeData ?? {...Conf.ThemeDefault, isEnabled: false};
this.updateApplicationField("themeData", {...theme, isEnabled: e.target.value});
}} >
@ -950,7 +947,7 @@ class ApplicationEditPage extends React.Component {
const signInUrl = `/login/oauth/authorize?client_id=${this.state.application.clientId}&response_type=code&redirect_uri=${redirectUri}&scope=read&state=casdoor`;
const maskStyle = {position: "absolute", top: "0px", left: "0px", zIndex: 10, height: "97%", width: "100%", background: "rgba(0,0,0,0.4)"};
if (!this.state.application.enablePassword) {
if (!Setting.isPasswordEnabled(this.state.application)) {
signUpUrl = signInUrl.replace("/login/oauth/authorize", "/signup/oauth/authorize");
}
@ -974,7 +971,7 @@ class ApplicationEditPage extends React.Component {
}}>
<div style={{position: "relative", width: previewWidth, border: "1px solid rgb(217,217,217)", boxShadow: "10px 10px 5px #888888", overflow: "auto"}}>
{
this.state.application.enablePassword ? (
Setting.isPasswordEnabled(this.state.application) ? (
<div className="loginBackground" style={{backgroundImage: `url(${this.state.application?.formBackgroundUrl})`, overflow: "auto"}}>
<SignupPage application={this.state.application} preview = "auto" />
</div>
@ -1049,6 +1046,7 @@ class ApplicationEditPage extends React.Component {
submitApplicationEdit(exitAfterSave) {
const application = Setting.deepCopy(this.state.application);
application.providers = application.providers?.filter(provider => this.state.providers.map(provider => provider.name).includes(provider.name));
application.signinMethods = application.signinMethods?.filter(signinMethod => ["Password", "Verification code", "WebAuthn", "LDAP"].includes(signinMethod.name));
ApplicationBackend.updateApplication("admin", this.state.applicationName, application)
.then((res) => {

View File

@ -46,6 +46,11 @@ class ApplicationListPage extends BaseListPage {
providers: [
{name: "provider_captcha_default", canSignUp: false, canSignIn: false, canUnlink: false, prompted: false, signupGroup: "", rule: ""},
],
SigninMethods: [
{name: "Password", displayName: "Password", rule: "All"},
{name: "Verification code", displayName: "Verification code", rule: "All"},
{name: "WebAuthn", displayName: "WebAuthn", rule: "None"},
],
signupItems: [
{name: "ID", visible: false, required: true, rule: "Random"},
{name: "Username", visible: true, required: true, rule: "None"},
@ -59,6 +64,7 @@ class ApplicationListPage extends BaseListPage {
cert: "cert-built-in",
redirectUris: ["http://localhost:9000/callback"],
tokenFormat: "JWT",
tokenFields: [],
expireInHours: 24 * 7,
refreshExpireInHours: 24 * 7,
formOffset: 2,

View File

@ -25,6 +25,7 @@ class BaseListPage extends React.Component {
super(props);
this.state = {
classes: props,
organizationName: this.props.match?.params.organizationName || Setting.getRequestOrganization(this.props.account),
data: [],
pagination: {
current: 1,
@ -39,6 +40,10 @@ class BaseListPage extends React.Component {
}
handleOrganizationChange = () => {
this.setState({
organizationName: this.props.match?.params.organizationName || Setting.getRequestOrganization(this.props.account),
});
const {pagination} = this.state;
this.fetch({pagination});
};

View File

@ -0,0 +1,282 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
import * as InvitationBackend from "./backend/InvitationBackend";
import * as OrganizationBackend from "./backend/OrganizationBackend";
import * as ApplicationBackend from "./backend/ApplicationBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
const {Option} = Select;
class InvitationEditPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
invitationName: props.match.params.invitationName,
invitation: null,
organizations: [],
applications: [],
mode: props.location.mode !== undefined ? props.location.mode : "edit",
};
}
UNSAFE_componentWillMount() {
this.getInvitation();
this.getOrganizations();
this.getApplicationsByOrganization(this.state.organizationName);
}
getInvitation() {
InvitationBackend.getInvitation(this.state.organizationName, this.state.invitationName)
.then((res) => {
if (res.data === null) {
this.props.history.push("/404");
return;
}
this.setState({
invitation: res.data,
});
});
}
getOrganizations() {
OrganizationBackend.getOrganizations("admin")
.then((res) => {
this.setState({
organizations: res.data || [],
});
});
}
getApplicationsByOrganization(organizationName) {
ApplicationBackend.getApplicationsByOrganization("admin", organizationName)
.then((res) => {
this.setState({
applications: res.data || [],
});
});
}
parseInvitationField(key, value) {
if ([""].includes(key)) {
value = Setting.myParseInt(value);
}
return value;
}
updateInvitationField(key, value) {
value = this.parseInvitationField(key, value);
const invitation = this.state.invitation;
invitation[key] = value;
this.setState({
invitation: invitation,
});
}
renderInvitation() {
const isCreatedByPlan = this.state.invitation.tag === "auto_created_invitation_for_plan";
return (
<Card size="small" title={
<div>
{this.state.mode === "add" ? i18next.t("invitation:New Invitation") : i18next.t("invitation:Edit Invitation")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
} 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"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} disabled={!Setting.isAdminUser(this.props.account) || isCreatedByPlan} value={this.state.invitation.owner} onChange={(value => {this.updateInvitationField("owner", value);})}>
{
this.state.organizations.map((organization, index) => <Option key={index} value={organization.name}>{organization.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Name"), i18next.t("general:Name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.invitation.name} disabled={isCreatedByPlan} onChange={e => {
this.updateInvitationField("name", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Display name"), i18next.t("general:Display name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.invitation.displayName} onChange={e => {
this.updateInvitationField("displayName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("invitation:Code"), i18next.t("invitation:Code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.invitation.code} onChange={e => {
this.updateInvitationField("code", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("invitation:Quota"), i18next.t("invitation:Quota - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber min={0} value={this.state.invitation.quota} onChange={value => {
this.updateInvitationField("quota", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("invitation:Used count"), i18next.t("invitation:Used count - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber min={0} max={this.state.invitation.quota} value={this.state.invitation.usedCount} onChange={value => {
this.updateInvitationField("usedCount", value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Application"), i18next.t("general:Application - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.invitation.application}
onChange={(value => {this.updateInvitationField("application", value);})}
options={this.state.applications.map((application) => Setting.getOption(application.name, application.name))
} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("signup:Username"), i18next.t("signup:Username - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.invitation.username} onChange={e => {
this.updateInvitationField("username", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Email"), i18next.t("general:Email - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.invitation.email} onChange={e => {
this.updateInvitationField("email", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Phone"), i18next.t("general:Phone - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.invitation.phone} onChange={e => {
this.updateInvitationField("phone", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.invitation.state} onChange={(value => {
this.updateInvitationField("state", value);
})}
options={[
{value: "Active", name: i18next.t("subscription:Active")},
{value: "Suspended", name: i18next.t("subscription:Suspended")},
].map((item) => Setting.getOption(item.name, item.value))}
/>
</Col>
</Row>
</Card>
);
}
submitInvitationEdit(exitAfterSave) {
const invitation = Setting.deepCopy(this.state.invitation);
InvitationBackend.updateInvitation(this.state.organizationName, this.state.invitationName, invitation)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully saved"));
this.setState({
invitationName: this.state.invitation.name,
});
if (exitAfterSave) {
this.props.history.push("/invitations");
} else {
this.props.history.push(`/invitations/${this.state.invitation.owner}/${this.state.invitation.name}`);
}
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.updateInvitationField("name", this.state.invitationName);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteInvitation() {
InvitationBackend.deleteInvitation(this.state.invitation)
.then((res) => {
if (res.status === "ok") {
this.props.history.push("/invitations");
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
render() {
return (
<div>
{
this.state.invitation !== null ? this.renderInvitation() : null
}
<div style={{marginTop: "20px", marginLeft: "40px"}}>
<Button size="large" onClick={() => this.submitInvitationEdit(false)}>{i18next.t("general:Save")}</Button>
<Button style={{marginLeft: "20px"}} type="primary" size="large" onClick={() => this.submitInvitationEdit(true)}>{i18next.t("general:Save & Exit")}</Button>
{this.state.mode === "add" ? <Button style={{marginLeft: "20px"}} size="large" onClick={() => this.deleteInvitation()}>{i18next.t("general:Cancel")}</Button> : null}
</div>
</div>
);
}
}
export default InvitationEditPage;

View File

@ -0,0 +1,310 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Link} from "react-router-dom";
import {Button, Table} from "antd";
import {MinusCircleOutlined, SyncOutlined} from "@ant-design/icons";
import moment from "moment";
import * as Setting from "./Setting";
import * as InvitationBackend from "./backend/InvitationBackend";
import i18next from "i18next";
import BaseListPage from "./BaseListPage";
import PopconfirmModal from "./common/modal/PopconfirmModal";
import copy from "copy-to-clipboard";
class InvitationListPage extends BaseListPage {
newInvitation() {
const randomName = Setting.getRandomName();
const owner = Setting.getRequestOrganization(this.props.account);
return {
owner: owner,
name: `invitation_${randomName}`,
createdTime: moment().format(),
updatedTime: moment().format(),
displayName: `New Invitation - ${randomName}`,
code: Math.random().toString(36).slice(-10),
quota: 1,
usedCount: 0,
application: "",
username: "",
email: "",
phone: "",
signupGroup: "",
state: "Active",
};
}
addInvitation() {
const newInvitation = this.newInvitation();
InvitationBackend.addInvitation(newInvitation)
.then((res) => {
if (res.status === "ok") {
this.props.history.push({pathname: `/invitations/${newInvitation.owner}/${newInvitation.name}`, mode: "add"});
Setting.showMessage("success", i18next.t("general:Successfully added"));
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to add")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
deleteInvitation(i) {
InvitationBackend.deleteInvitation(this.state.data[i])
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", i18next.t("general:Successfully deleted"));
this.setState({
data: Setting.deleteRow(this.state.data, i),
pagination: {total: this.state.pagination.total - 1},
});
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to delete")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}: ${error}`);
});
}
renderTable(invitations) {
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "140px",
fixed: "left",
sorter: true,
...this.getColumnSearchProps("name"),
render: (text, record, index) => {
return (
<Link to={`/invitations/${record.owner}/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Organization"),
dataIndex: "owner",
key: "owner",
width: "150px",
sorter: true,
...this.getColumnSearchProps("owner"),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
{text}
</Link>
);
},
},
// {
// title: i18next.t("general:Created time"),
// dataIndex: "createdTime",
// key: "createdTime",
// width: "160px",
// sorter: true,
// render: (text, record, index) => {
// return Setting.getFormattedDate(text);
// },
// },
{
title: i18next.t("general:Updated time"),
dataIndex: "updatedTime",
key: "updatedTime",
width: "160px",
sorter: true,
render: (text, record, index) => {
return Setting.getFormattedDate(text);
},
},
{
title: i18next.t("general:Display name"),
dataIndex: "displayName",
key: "displayName",
width: "170px",
sorter: true,
...this.getColumnSearchProps("displayName"),
},
{
title: i18next.t("invitation:Code"),
dataIndex: "code",
key: "code",
width: "160px",
sorter: true,
...this.getColumnSearchProps("code"),
},
{
title: i18next.t("invitation:Quota"),
dataIndex: "quota",
key: "quota",
width: "120px",
sorter: true,
...this.getColumnSearchProps("quota"),
},
{
title: i18next.t("invitation:Used count"),
dataIndex: "usedCount",
key: "usedCount",
width: "130px",
sorter: true,
...this.getColumnSearchProps("usedCount"),
},
{
title: i18next.t("general:Application"),
dataIndex: "application",
key: "application",
width: "170px",
sorter: true,
...this.getColumnSearchProps("application"),
render: (text, record, index) => {
return (
<Link to={`/applications/${record.owner}/${text}`}>
{text}
</Link>
);
},
},
{
title: i18next.t("general:Email"),
dataIndex: "email",
key: "email",
width: "160px",
sorter: true,
...this.getColumnSearchProps("email"),
render: (text, record, index) => {
return (
<a href={`mailto:${text}`}>
{text}
</a>
);
},
},
{
title: i18next.t("general:Phone"),
dataIndex: "phone",
key: "phone",
width: "120px",
sorter: true,
...this.getColumnSearchProps("phone"),
},
{
title: i18next.t("general:State"),
dataIndex: "state",
key: "state",
width: "120px",
sorter: true,
...this.getColumnSearchProps("state"),
render: (text, record, index) => {
switch (text) {
case "Active":
return Setting.getTag("success", i18next.t("subscription:Active"), <SyncOutlined spin />);
case "Suspended":
return Setting.getTag("default", i18next.t("subscription:Suspended"), <MinusCircleOutlined />);
default:
return null;
}
},
},
{
title: i18next.t("general:Action"),
dataIndex: "",
key: "op",
width: "350px",
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => {
copy(`${window.location.origin}/login/${record.owner}?invitation_code=${record.code}`);
Setting.showMessage("success", i18next.t("general:Copied to clipboard successfully"));
}}>
{i18next.t("application:Copy signup page URL")}
</Button>
<Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/invitations/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<PopconfirmModal
title={i18next.t("general:Sure to delete") + `: ${record.name} ?`}
onConfirm={() => this.deleteInvitation(index)}
>
</PopconfirmModal>
</div>
);
},
},
];
const paginationProps = {
total: this.state.pagination.total,
showQuickJumper: true,
showSizeChanger: true,
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
};
return (
<div>
<Table scroll={{x: "max-content"}} columns={columns} dataSource={invitations} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
title={() => (
<div>
{i18next.t("general:Invitations")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addInvitation.bind(this)}>{i18next.t("general:Add")}</Button>
</div>
)}
loading={this.state.loading}
onChange={this.handleTableChange}
/>
</div>
);
}
fetch = (params = {}) => {
let field = params.searchedColumn, value = params.searchText;
const sortField = params.sortField, sortOrder = params.sortOrder;
if (params.type !== undefined && params.type !== null) {
field = "type";
value = params.type;
}
this.setState({loading: true});
InvitationBackend.getInvitations(Setting.isDefaultOrganizationSelected(this.props.account) ? "" : Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
.then((res) => {
this.setState({
loading: false,
});
if (res.status === "ok") {
this.setState({
data: res.data,
pagination: {
...params.pagination,
total: res.data2,
},
searchText: params.searchText,
searchedColumn: params.searchedColumn,
});
} else {
if (Setting.isResponseDenied(res)) {
this.setState({
isAuthorized: false,
});
} else {
Setting.showMessage("error", res.msg);
}
}
});
};
}
export default InvitationListPage;

View File

@ -393,7 +393,7 @@ class OrganizationEditPage extends React.Component {
</Col>
<Col span={22} style={{marginTop: "5px"}}>
<Row>
<Radio.Group value={this.state.organization.themeData?.isEnabled ?? false} onChange={e => {
<Radio.Group buttonStyle="solid" value={this.state.organization.themeData?.isEnabled ?? false} onChange={e => {
const {_, ...theme} = this.state.organization.themeData ?? {...Conf.ThemeDefault, isEnabled: false};
this.updateOrganizationField("themeData", {...theme, isEnabled: e.target.value});
}} >

View File

@ -32,7 +32,6 @@ class PricingEditPage extends React.Component {
organizationName: props.organizationName !== undefined ? props.organizationName : props.match.params.organizationName,
pricingName: props.match.params.pricingName,
organizations: [],
application: null,
applications: [],
pricing: null,
plans: [],

View File

@ -29,6 +29,14 @@ import {CaptchaPreview} from "./common/CaptchaPreview";
import {CountryCodeSelect} from "./common/select/CountryCodeSelect";
import * as Web3Auth from "./auth/Web3Auth";
import {Controlled as CodeMirror} from "react-codemirror2";
import "codemirror/lib/codemirror.css";
require("codemirror/theme/material-darker.css");
require("codemirror/mode/htmlmixed/htmlmixed");
require("codemirror/mode/xml/xml");
require("codemirror/mode/css/css");
const {Option} = Select;
const {TextArea} = Input;
@ -480,7 +488,7 @@ class ProviderEditPage extends React.Component {
this.updateProviderField("port", 465);
this.updateProviderField("disableSsl", false);
this.updateProviderField("title", "Casdoor Verification Code");
this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.");
this.updateProviderField("content", Setting.getDefaultHtmlEmailContent());
this.updateProviderField("receiver", this.props.account.email);
} else if (value === "SMS") {
this.updateProviderField("type", "Twilio SMS");
@ -788,7 +796,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
{["Custom HTTP SMS", "Local File System", "MinIO", "Tencent Cloud COS", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology"].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"))} :
@ -824,7 +832,7 @@ class ProviderEditPage extends React.Component {
</Col>
</Row>
)}
{["Custom HTTP SMS", "MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo"].includes(this.state.provider.type) ? null : (
{["Custom HTTP SMS", "MinIO", "Google Cloud Storage", "Qiniu Cloud Kodo", "Synology"].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"))} :
@ -966,22 +974,47 @@ class ProviderEditPage extends React.Component {
{Setting.getLabel(i18next.t("provider:Email content"), i18next.t("provider:Email content - Tooltip"))} :
</Col>
<Col span={22} >
<TextArea autoSize={{minRows: 3, maxRows: 100}} value={this.state.provider.content} onChange={e => {
this.updateProviderField("content", e.target.value);
}} />
<Row style={{marginTop: "20px"}} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => this.updateProviderField("content", "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes.")} >
{i18next.t("provider:Reset to Default Text")}
</Button>
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => this.updateProviderField("content", Setting.getDefaultHtmlEmailContent())} >
{i18next.t("provider:Reset to Default HTML")}
</Button>
</Row>
<Row>
<Col span={Setting.isMobile() ? 22 : 11}>
<div style={{height: "300px", margin: "10px"}}>
<CodeMirror
value={this.state.provider.content}
options={{mode: "htmlmixed", theme: "material-darker"}}
onBeforeChange={(editor, data, value) => {
this.updateProviderField("content", value);
}}
/>
</div>
</Col>
<Col span={1} />
<Col span={Setting.isMobile() ? 22 : 11}>
<div style={{margin: "10px"}}>
<div dangerouslySetInnerHTML={{__html: this.state.provider.content.replace("%s", "123456").replace("%{user.friendlyName}", Setting.getFriendlyUserName(this.props.account))}} />
</div>
</Col>
</Row>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Test Email"), i18next.t("provider:Test Email - Tooltip"))} :
</Col>
<Col span={4} >
<Input value={this.state.provider.receiver} placeholder = {i18next.t("user:Input your email")} onChange={e => {
this.updateProviderField("receiver", e.target.value);
}} />
<Col span={4}>
<Input value={this.state.provider.receiver} placeholder={i18next.t("user:Input your email")}
onChange={e => {
this.updateProviderField("receiver", e.target.value);
}} />
</Col>
{["Azure ACS"].includes(this.state.provider.type) ? null : (
<Button style={{marginLeft: "10px", marginBottom: "5px"}} type="primary" onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
<Button style={{marginLeft: "10px", marginBottom: "5px"}} onClick={() => ProviderEditTestEmail.connectSmtpServer(this.state.provider)} >
{i18next.t("provider:Test SMTP Connection")}
</Button>
)}

View File

@ -207,6 +207,10 @@ export const OtherProviderInfo = {
logo: `${StaticBaseUrl}/img/social_google_cloud.png`,
url: "https://cloud.google.com/storage",
},
"Synology": {
logo: `${StaticBaseUrl}/img/social_synology.png`,
url: "https://www.synology.com/en-global/dsm/feature/file_sharing",
},
},
SAML: {
"Aliyun IDaaS": {
@ -1024,6 +1028,7 @@ export function getProviderTypeOptions(category) {
{id: "Azure Blob", name: "Azure Blob"},
{id: "Qiniu Cloud Kodo", name: "Qiniu Cloud Kodo"},
{id: "Google Cloud Storage", name: "Google Cloud Storage"},
{id: "Synology", name: "Synology"},
]
);
} else if (category === "SAML") {
@ -1131,11 +1136,35 @@ export function renderLogo(application) {
}
}
function isSigninMethodEnabled(application, signinMethod) {
if (application && application.signinMethods) {
return application.signinMethods.filter(item => item.name === signinMethod).length > 0;
} else {
return false;
}
}
export function isPasswordEnabled(application) {
return isSigninMethodEnabled(application, "Password");
}
export function isCodeSigninEnabled(application) {
return isSigninMethodEnabled(application, "Verification code");
}
export function isWebAuthnEnabled(application) {
return isSigninMethodEnabled(application, "WebAuthn");
}
export function isLdapEnabled(application) {
return isSigninMethodEnabled(application, "LDAP");
}
export function getLoginLink(application) {
let url;
if (application === null) {
url = null;
} else if (!application.enablePassword && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
} else if (!isPasswordEnabled(application) && window.location.pathname.includes("/auto-signup/oauth/authorize")) {
url = window.location.href.replace("/auto-signup/oauth/authorize", "/login/oauth/authorize");
} else if (authConfig.appName === application.name) {
url = "/login";
@ -1191,7 +1220,7 @@ export function renderSignupLink(application, text) {
let url;
if (application === null) {
url = null;
} else if (!application.enablePassword && window.location.pathname.includes("/login/oauth/authorize")) {
} else if (!isPasswordEnabled(application) && window.location.pathname.includes("/login/oauth/authorize")) {
url = window.location.href.replace("/login/oauth/authorize", "/auto-signup/oauth/authorize");
} else if (authConfig.appName === application.name) {
url = "/signup";
@ -1405,3 +1434,60 @@ export function getCurrencySymbol(currency) {
return currency;
}
}
export function getFriendlyUserName(account) {
if (account.firstName !== "" && account.lastName !== "") {
return `${account.firstName}, ${account.lastName}`;
} else if (account.displayName !== "") {
return account.displayName;
} else if (account.name !== "") {
return account.name;
} else {
return account.id;
}
}
export function getUserCommonFields() {
return ["Owner", "Name", "CreatedTime", "UpdatedTime", "Id", "Type", "Password", "PasswordSalt", "DisplayName", "FirstName", "LastName", "Avatar", "PermanentAvatar",
"Email", "EmailVerified", "Phone", "Location", "Address", "Affiliation", "Title", "IdCardType", "IdCard", "Homepage", "Bio", "Tag", "Region",
"Language", "Gender", "Birthday", "Education", "Score", "Ranking", "IsDefaultAvatar", "IsOnline", "IsAdmin", "IsForbidden", "IsDeleted", "CreatedIp",
"PreferredMfaType", "TotpSecret", "SignupApplication"];
}
export function getDefaultHtmlEmailContent() {
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verification Code Email</title>
<style>
body { font-family: Arial, sans-serif; }
.email-container { width: 600px; margin: 0 auto; }
.header { text-align: center; }
.code { font-size: 24px; margin: 20px 0; text-align: center; }
.footer { font-size: 12px; text-align: center; margin-top: 50px; }
.footer a { color: #000; text-decoration: none; }
</style>
</head>
<body>
<div class="email-container">
<div class="header">
<h3>Casbin Organization</h3>
<img src="https://cdn.casbin.org/img/casdoor-logo_1185x256.png" alt="Casdoor Logo" width="300">
</div>
<p><strong>%{user.friendlyName}</strong>, here is your verification code</p>
<p>Use this code for your transaction. It's valid for 5 minutes</p>
<div class="code">
%s
</div>
<p>Thanks</p>
<p>Casbin Team</p>
<hr>
<div class="footer">
<p>Casdoor is a brand operated by Casbin organization. For more info please refer to <a href="https://casdoor.org">https://casdoor.org</a></p>
</div>
</div>
</body>
</html>`;
}

View File

@ -30,7 +30,6 @@ class UserListPage extends BaseListPage {
super(props);
this.state = {
...this.state,
organizationName: this.props.organizationName ?? this.props.match?.params.organizationName ?? this.props.account.owner,
organization: null,
};
}
@ -62,7 +61,7 @@ class UserListPage extends BaseListPage {
newUser() {
const randomName = Setting.getRandomName();
const owner = Setting.isDefaultOrganizationSelected(this.props.account) ? this.state.organizationName : Setting.getRequestOrganization(this.props.account);
const owner = (Setting.isDefaultOrganizationSelected(this.props.account) || this.props.groupName) ? this.state.organizationName : Setting.getRequestOrganization(this.props.account);
return {
owner: owner,
name: `user_${randomName}`,
@ -85,7 +84,7 @@ class UserListPage extends BaseListPage {
score: this.state.organization.initScore,
isDeleted: false,
properties: {},
signupApplication: "app-built-in",
signupApplication: this.state.organization.defaultApplication,
};
}

View File

@ -146,7 +146,7 @@ export function getWechatMessageEvent() {
}
export function getCaptchaStatus(values) {
return fetch(`${Setting.ServerUrl}/api/get-captcha-status?organization=${values["organization"]}&user_id=${values["username"]}`, {
return fetch(`${Setting.ServerUrl}/api/get-captcha-status?organization=${values["organization"]}&userId=${values["username"]}`, {
method: "GET",
credentials: "include",
headers: {

View File

@ -201,19 +201,35 @@ class LoginPage extends React.Component {
}
getDefaultLoginMethod(application) {
if (application?.enablePassword) {
return "password";
}
if (application?.enableCodeSignin) {
return "verificationCode";
}
if (application?.enableWebAuthn) {
return "webAuthn";
if (application?.signinMethods?.length > 0) {
switch (application?.signinMethods[0].name) {
case "Password": return "password";
case "Verification code": {
switch (application?.signinMethods[0].rule) {
case "All": return "verificationCode"; // All
case "Email only": return "verificationCodeEmail";
case "Phone only": return "verificationCodePhone";
}
break;
}
case "WebAuthn": return "webAuthn";
case "LDAP": return "ldap";
}
}
return "password";
}
getPlaceholder() {
switch (this.state.loginMethod) {
case "verificationCode": return i18next.t("login:Email or phone");
case "verificationCodeEmail": return i18next.t("login:Email");
case "verificationCodePhone": return i18next.t("login:Phone");
case "ldap": return i18next.t("login:LDAP username, Email or phone");
default: return i18next.t("login:username, Email or phone");
}
}
onUpdateAccount(account) {
this.props.onUpdateAccount(account);
}
@ -239,6 +255,15 @@ class LoginPage extends React.Component {
values["organization"] = this.getApplicationObj().organization;
}
if (this.state.loginMethod === "password") {
values["signinMethod"] = "Password";
} else if (this.state.loginMethod?.includes("verificationCode")) {
values["signinMethod"] = "Verification code";
} else if (this.state.loginMethod === "webAuthn") {
values["signinMethod"] = "WebAuthn";
} else if (this.state.loginMethod === "ldap") {
values["signinMethod"] = "LDAP";
}
const oAuthParams = Util.getOAuthGetParameters();
values["type"] = oAuthParams?.responseType ?? this.state.type;
@ -315,7 +340,7 @@ class LoginPage extends React.Component {
this.signInWithWebAuthn(username, values);
return;
}
if (this.state.loginMethod === "password") {
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
if (this.state.enableCaptchaModal === CaptchaRule.Always) {
this.setState({
openCaptchaModal: true,
@ -454,6 +479,10 @@ class LoginPage extends React.Component {
}
renderOtherFormProvider(application) {
if (Setting.inIframe()) {
return null;
}
for (const providerConf of application.providers) {
if (providerConf.provider?.type === "Google" && providerConf.rule === "OneTap" && this.props.preview !== "auto") {
return (
@ -461,6 +490,8 @@ class LoginPage extends React.Component {
);
}
}
return null;
}
renderForm(application) {
@ -487,7 +518,7 @@ class LoginPage extends React.Component {
);
}
const showForm = application.enablePassword || application.enableCodeSignin || application.enableWebAuthn;
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application);
if (showForm) {
let loginWidth = 320;
if (Setting.getLanguage() === "fr") {
@ -546,10 +577,21 @@ class LoginPage extends React.Component {
rules={[
{
required: true,
message: i18next.t("login:Please input your Email or Phone!"),
message: () => {
switch (this.state.loginMethod) {
case "verificationCodeEmail": return i18next.t("login:Please input your Email!");
case "verificationCodePhone": return i18next.t("login:Please input your Phone!");
case "ldap": return i18next.t("login:Please input your LDAP username!");
default: return i18next.t("login:Please input your Email or Phone!");
}
},
},
{
validator: (_, value) => {
if (value === "") {
return Promise.resolve();
}
if (this.state.loginMethod === "verificationCode") {
if (!Setting.isValidEmail(value) && !Setting.isValidPhone(value)) {
this.setState({validEmailOrPhone: false});
@ -561,6 +603,19 @@ class LoginPage extends React.Component {
} else {
this.setState({validEmail: false});
}
} else if (this.state.loginMethod === "verificationCodeEmail") {
if (!Setting.isValidEmail(value)) {
this.setState({validEmail: false});
this.setState({validEmailOrPhone: false});
return Promise.reject(i18next.t("login:The input is not valid Email!"));
} else {
this.setState({validEmail: true});
}
} else if (this.state.loginMethod === "verificationCodePhone") {
if (!Setting.isValidPhone(value)) {
this.setState({validEmailOrPhone: false});
return Promise.reject(i18next.t("login:The input is not valid phone number!"));
}
}
this.setState({validEmailOrPhone: true});
@ -572,7 +627,7 @@ class LoginPage extends React.Component {
<Input
id="input"
prefix={<UserOutlined className="site-form-item-icon" />}
placeholder={(this.state.loginMethod === "verificationCode") ? i18next.t("login:Email or phone") : i18next.t("login:username, Email or phone")}
placeholder={this.getPlaceholder()}
onChange={e => {
this.setState({
username: e.target.value,
@ -831,7 +886,7 @@ class LoginPage extends React.Component {
renderPasswordOrCodeInput() {
const application = this.getApplicationObj();
if (this.state.loginMethod === "password") {
if (this.state.loginMethod === "password" || this.state.loginMethod === "ldap") {
return (
<Col span={24}>
<Form.Item
@ -842,12 +897,12 @@ class LoginPage extends React.Component {
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder={i18next.t("general:Password")}
disabled={!application.enablePassword}
disabled={this.state.loginMethod === "password" ? !Setting.isPasswordEnabled(application) : !Setting.isLdapEnabled(application)}
/>
</Form.Item>
</Col>
);
} else if (this.state.loginMethod === "verificationCode") {
} else if (this.state.loginMethod?.includes("verificationCode")) {
return (
<Col span={24}>
<Form.Item
@ -871,9 +926,28 @@ class LoginPage extends React.Component {
renderMethodChoiceBox() {
const application = this.getApplicationObj();
const items = [];
application.enablePassword ? items.push({label: i18next.t("general:Password"), key: "password"}) : null;
application.enableCodeSignin ? items.push({label: i18next.t("login:Verification code"), key: "verificationCode"}) : null;
application.enableWebAuthn ? items.push({label: i18next.t("login:WebAuthn"), key: "webAuthn"}) : null;
const generateItemKey = (name, rule) => {
return `${name}-${rule}`;
};
const itemsMap = new Map([
[generateItemKey("Password", "All"), {label: i18next.t("general:Password"), key: "password"}],
[generateItemKey("Password", "Non-LDAP"), {label: i18next.t("general:Password"), key: "password"}],
[generateItemKey("Verification code", "All"), {label: i18next.t("login:Verification code"), key: "verificationCode"}],
[generateItemKey("Verification code", "Email only"), {label: i18next.t("login:Verification code"), key: "verificationCodeEmail"}],
[generateItemKey("Verification code", "Phone only"), {label: i18next.t("login:Verification code"), key: "verificationCodePhone"}],
[generateItemKey("WebAuthn", "None"), {label: i18next.t("login:WebAuthn"), key: "webAuthn"}],
[generateItemKey("LDAP", "None"), {label: i18next.t("login:LDAP"), key: "ldap"}],
]);
application?.signinMethods?.forEach((signinMethod) => {
const item = itemsMap.get(generateItemKey(signinMethod.name, signinMethod.rule));
if (item) {
const label = signinMethod.name === signinMethod.displayName ? item.label : signinMethod.displayName;
items.push({label: label, key: item.key});
}
});
if (items.length > 1) {
return (
@ -1003,7 +1077,7 @@ class LoginPage extends React.Component {
}
const visibleOAuthProviderItems = (application.providers === null) ? [] : application.providers.filter(providerItem => this.isProviderVisible(providerItem));
if (this.props.preview !== "auto" && !application.enablePassword && !application.enableCodeSignin && !application.enableWebAuthn && visibleOAuthProviderItems.length === 1) {
if (this.props.preview !== "auto" && !Setting.isPasswordEnabled(application) && !Setting.isCodeSigninEnabled(application) && !Setting.isWebAuthnEnabled(application) && !Setting.isLdapEnabled(application) && visibleOAuthProviderItems.length === 1) {
Setting.goToLink(Provider.getAuthUrl(application, visibleOAuthProviderItems[0].provider, "signup"));
return (
<div style={{display: "flex", justifyContent: "center", alignItems: "center", width: "100%"}}>

View File

@ -0,0 +1,81 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as Setting from "../Setting";
export function getInvitations(owner, page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-invitations?owner=${owner}&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function getInvitation(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-invitation?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function updateInvitation(owner, name, invitation) {
const newInvitation = Setting.deepCopy(invitation);
return fetch(`${Setting.ServerUrl}/api/update-invitation?id=${owner}/${encodeURIComponent(name)}`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newInvitation),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function addInvitation(invitation) {
const newInvitation = Setting.deepCopy(invitation);
return fetch(`${Setting.ServerUrl}/api/add-invitation`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newInvitation),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function deleteInvitation(invitation) {
const newInvitation = Setting.deepCopy(invitation);
return fetch(`${Setting.ServerUrl}/api/delete-invitation`, {
method: "POST",
credentials: "include",
body: JSON.stringify(newInvitation),
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function verifyInvitation(owner, name) {
return fetch(`${Setting.ServerUrl}/api/verify-invitation?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}

View File

@ -33,7 +33,7 @@ export function connectSmtpServer(provider) {
testEmailProvider(provider)
.then((res) => {
if (res.status === "ok") {
Setting.showMessage("success", "provider:SMTP connected successfully");
Setting.showMessage("success", i18next.t("provider:SMTP connected successfully"));
} else {
Setting.showMessage("error", res.msg);
}

View File

@ -36,12 +36,6 @@
"Enable SAML C14N10 - Tooltip": "Enable SAML C14N10 - Tooltip",
"Enable SAML compression": "Enable SAML compression",
"Enable SAML compression - Tooltip": "Whether to compress SAML response messages when Casdoor is used as SAML idp",
"Enable WebAuthn signin": "Enable WebAuthn signin",
"Enable WebAuthn signin - Tooltip": "Whether to allow users to login with WebAuthn",
"Enable code signin": "Enable code signin",
"Enable code signin - Tooltip": "Whether to allow users to login with phone or Email verification code",
"Enable password": "Enable password",
"Enable password - Tooltip": "Whether to allow users to login with password",
"Enable side panel": "Enable side panel",
"Enable signin session - Tooltip": "Whether Casdoor maintains a session after logging into Casdoor from the application",
"Enable signup": "Enable signup",
@ -100,6 +94,8 @@
"Sign Up Error": "Sign Up Error",
"Signin": "Signin",
"Signin (Default True)": "Signin (Default True)",
"Signin methods": "Signin methods",
"Signin methods - Tooltip": "Signin methods - Tooltip",
"Signin session": "Signin session",
"Signup items": "Signup items",
"Signup items - Tooltip": "Items for users to fill in when registering new accounts",
@ -107,6 +103,8 @@
"The application does not allow to sign up new account": "The application does not allow to sign up new account",
"Token expire": "Token expire",
"Token expire - Tooltip": "Access token expiration time",
"Token fields": "Token fields",
"Token fields - Tooltip": "Token fields - Tooltip",
"Token format": "Token format",
"Token format - Tooltip": "The format of access token",
"You are unexpected to see this prompt page": "You are unexpected to see this prompt page"
@ -173,6 +171,7 @@
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
"Affiliation URL - Tooltip": "The homepage URL for the affiliation",
"All": "All",
"Application": "Application",
"Application - Tooltip": "Application - Tooltip",
"Applications": "Applications",
@ -214,6 +213,7 @@
"Edit": "Edit",
"Email": "Email",
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
@ -242,6 +242,7 @@
"ID": "ID",
"ID - Tooltip": "Unique random string",
"Identity": "Identity",
"Invitations": "Invitations",
"Is enabled": "Is enabled",
"Is enabled - Tooltip": "Set whether it can use",
"LDAPs": "LDAPs",
@ -266,6 +267,8 @@
"Models": "Models",
"Name": "Name",
"Name - Tooltip": "Unique, string-based ID",
"Name format": "Name format",
"Non-LDAP": "Non-LDAP",
"None": "None",
"OAuth providers": "OAuth providers",
"OK": "OK",
@ -287,6 +290,7 @@
"Permissions - Tooltip": "Permissions owned by this user",
"Phone": "Phone",
"Phone - Tooltip": "Phone number",
"Phone only": "Phone only",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Plans",
@ -383,6 +387,16 @@
"Past 30 Days": "Past 30 Days",
"Total users": "Total users"
},
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",
"Quota - Tooltip": "Quota - Tooltip",
"Used count": "Used count",
"Used count - Tooltip": "Used count - Tooltip"
},
"ldap": {
"Admin": "Admin",
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
@ -416,16 +430,23 @@
"login": {
"Auto sign in": "Auto sign in",
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
"LDAP": "LDAP",
"LDAP username, Email or phone": "LDAP username, Email or phone",
"Loading": "Loading",
"Logging out...": "Logging out...",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
"Please input your Phone!": "Please input your Phone!",
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
@ -439,6 +460,8 @@
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
"To access": "To access",
"Verification code": "Verification code",
"WebAuthn": "WebAuthn",
@ -751,11 +774,14 @@
"Region endpoint for Internet": "Region endpoint for Internet",
"Region endpoint for Intranet": "Region endpoint for Intranet",
"Required": "Required",
"Reset to Default HTML": "Reset to Default HTML",
"Reset to Default Text": "Reset to Default Text",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "Phone number for sending test SMS",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account",
"SMTP connected successfully": "SMTP connected successfully",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL",
"SP Entity ID": "SP Entity ID",
@ -862,6 +888,7 @@
"Please input your real name!": "Please input your real name!",
"Please select your country code!": "Please select your country code!",
"Please select your country/region!": "Please select your country/region!",
"Regex": "Regex",
"Terms of Use": "Terms of Use",
"Terms of Use - Tooltip": "Terms of use that users need to read and agree to during registration",
"Text 1": "Text 1",
@ -1028,8 +1055,6 @@
"Managed accounts": "Managed accounts",
"Modify password...": "Modify password...",
"Multi-factor authentication": "Multi-factor authentication",
"Name": "Name",
"Name format": "Name format",
"New Email": "New Email",
"New Password": "New Password",
"New User": "New User",
@ -1067,7 +1092,6 @@
"Upload ID card front picture": "Upload ID card front picture",
"Upload ID card with person picture": "Upload ID card with person picture",
"Upload a photo": "Upload a photo",
"Value": "Value",
"Values": "Values",
"Verification code sent": "Verification code sent",
"WebAuthn credentials": "WebAuthn credentials",

View File

@ -36,12 +36,6 @@
"Enable SAML C14N10 - Tooltip": "Enable SAML C14N10 - Tooltip",
"Enable SAML compression": "Aktivieren Sie SAML-Komprimierung",
"Enable SAML compression - Tooltip": "Ob SAML-Antwortnachrichten komprimiert werden sollen, wenn Casdoor als SAML-IdP verwendet wird",
"Enable WebAuthn signin": "Anmeldung mit WebAuthn aktivieren",
"Enable WebAuthn signin - Tooltip": "Ob Benutzern erlaubt werden soll, sich mit WebAuthn anzumelden",
"Enable code signin": "Code Anmeldung aktivieren",
"Enable code signin - Tooltip": "Ob Benutzern erlaubt werden soll, sich mit einem Telefon- oder E-Mail-Bestätigungscode anzumelden",
"Enable password": "Passwort aktivieren",
"Enable password - Tooltip": "Ob Benutzern erlaubt werden soll, sich mit einem Passwort anzumelden",
"Enable side panel": "Sidepanel aktivieren",
"Enable signin session - Tooltip": "Ob Casdoor eine Sitzung aufrechterhält, nachdem man sich von der Anwendung aus bei Casdoor angemeldet hat",
"Enable signup": "Registrierung aktivieren",
@ -100,6 +94,8 @@
"Sign Up Error": "Registrierungsfehler",
"Signin": "Signin",
"Signin (Default True)": "Signin (Default True)",
"Signin methods": "Signin methods",
"Signin methods - Tooltip": "Signin methods - Tooltip",
"Signin session": "Anmeldesession",
"Signup items": "Registrierungs Items",
"Signup items - Tooltip": "Items, die Benutzer ausfüllen müssen, wenn sie neue Konten registrieren",
@ -107,6 +103,8 @@
"The application does not allow to sign up new account": "Die Anwendung erlaubt es nicht, ein neues Konto zu registrieren",
"Token expire": "Token läuft ab",
"Token expire - Tooltip": "Ablaufzeit des Access-Tokens",
"Token fields": "Token fields",
"Token fields - Tooltip": "Token fields - Tooltip",
"Token format": "Token-Format",
"Token format - Tooltip": "Das Format des Access-Tokens",
"You are unexpected to see this prompt page": "Sie sind unerwartet auf diese Aufforderungsseite gelangt"
@ -173,6 +171,7 @@
"Admin": "Admin",
"Affiliation URL": "Zugehörigkeits-URL",
"Affiliation URL - Tooltip": "Die Homepage-URL für die Zugehörigkeit",
"All": "All",
"Application": "Applikation",
"Application - Tooltip": "Application - Tooltip",
"Applications": "Anwendungen",
@ -214,6 +213,7 @@
"Edit": "Bearbeiten",
"Email": "E-Mail",
"Email - Tooltip": "Gültige E-Mail-Adresse",
"Email only": "Email only",
"Enable": "Enable",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
@ -242,6 +242,7 @@
"ID": "ID",
"ID - Tooltip": "Einzigartiger Zufallsstring",
"Identity": "Identity",
"Invitations": "Invitations",
"Is enabled": "Ist aktiviert",
"Is enabled - Tooltip": "Festlegen, ob es verwendet werden kann",
"LDAPs": "LDAPs",
@ -266,6 +267,8 @@
"Models": "Modelle",
"Name": "Name",
"Name - Tooltip": "Eindeutige, auf Strings basierende ID",
"Name format": "Name format",
"Non-LDAP": "Non-LDAP",
"None": "None",
"OAuth providers": "OAuth-Provider",
"OK": "OK",
@ -287,6 +290,7 @@
"Permissions - Tooltip": "Berechtigungen, die diesem Benutzer gehören",
"Phone": "Telefon",
"Phone - Tooltip": "Telefonnummer",
"Phone only": "Phone only",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Pläne",
@ -383,6 +387,16 @@
"Past 30 Days": "Past 30 Days",
"Total users": "Total users"
},
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",
"Quota - Tooltip": "Quota - Tooltip",
"Used count": "Used count",
"Used count - Tooltip": "Used count - Tooltip"
},
"ldap": {
"Admin": "Admin",
"Admin - Tooltip": "CN oder ID des LDAP-Serveradministrators",
@ -416,16 +430,23 @@
"login": {
"Auto sign in": "Automatische Anmeldung",
"Continue with": "Weitermachen mit",
"Email": "Email",
"Email or phone": "E-Mail oder Telefon",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Passwort vergessen?",
"LDAP": "LDAP",
"LDAP username, Email or phone": "LDAP username, Email or phone",
"Loading": "Laden",
"Logging out...": "Ausloggen...",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"No account?": "Kein Konto?",
"Or sign in with another account": "Oder mit einem anderen Konto anmelden",
"Phone": "Phone",
"Please input your Email or Phone!": "Bitte geben Sie Ihre E-Mail oder Telefonnummer ein!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
"Please input your Phone!": "Please input your Phone!",
"Please input your code!": "Bitte geben Sie Ihren Code ein!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Bitte geben Sie Ihr Passwort ein!",
@ -439,6 +460,8 @@
"Signing in...": "Anmelden...",
"Successfully logged in with WebAuthn credentials": "Erfolgreich mit WebAuthn-Anmeldeinformationen angemeldet",
"The input is not valid Email or phone number!": "Die Eingabe ist keine gültige E-Mail-Adresse oder Telefonnummer!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
"To access": "Zum Zugriff",
"Verification code": "Verifizierungscode",
"WebAuthn": "WebAuthn",
@ -751,11 +774,14 @@
"Region endpoint for Internet": "Regionsendpunkt für das Internet",
"Region endpoint for Intranet": "Regionales Endpunkt für Intranet",
"Required": "Benötigt",
"Reset to Default HTML": "Reset to Default HTML",
"Reset to Default Text": "Reset to Default Text",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpunkt (HTTP)",
"SMS Test": "SMS-Test",
"SMS Test - Tooltip": "Telefonnummer für den Versand von Test-SMS",
"SMS account": "SMS-Konto",
"SMS account - Tooltip": "SMS-Konto",
"SMTP connected successfully": "SMTP connected successfully",
"SP ACS URL": "SP-ACS-URL",
"SP ACS URL - Tooltip": "SP ACS URL - Tooltip",
"SP Entity ID": "SP-Entitäts-ID",
@ -862,6 +888,7 @@
"Please input your real name!": "Bitte geben Sie Ihren richtigen Namen ein!",
"Please select your country code!": "Bitte wählen Sie Ihren Ländercode aus!",
"Please select your country/region!": "Bitte wählen Sie Ihr Land/Ihre Region aus!",
"Regex": "Regex",
"Terms of Use": "Nutzungsbedingungen",
"Terms of Use - Tooltip": "Nutzungsbedingungen, die Benutzer während der Registrierung lesen und akzeptieren müssen",
"Text 1": "Text 1",
@ -1028,8 +1055,6 @@
"Managed accounts": "Verwaltete Konten",
"Modify password...": "Passwort ändern...",
"Multi-factor authentication": "Multi-factor authentication",
"Name": "Name",
"Name format": "Name format",
"New Email": "Neue E-Mail",
"New Password": "Neues Passwort",
"New User": "Neuer Benutzer",
@ -1067,7 +1092,6 @@
"Upload ID card front picture": "Upload ID card front picture",
"Upload ID card with person picture": "Upload ID card with person picture",
"Upload a photo": "Lade ein Foto hoch",
"Value": "Value",
"Values": "Werte",
"Verification code sent": "Bestätigungscode gesendet",
"WebAuthn credentials": "WebAuthn-Anmeldeinformationen",

View File

@ -36,12 +36,6 @@
"Enable SAML C14N10 - Tooltip": "Use C14N10 instead of C14N11 in SAML",
"Enable SAML compression": "Enable SAML compression",
"Enable SAML compression - Tooltip": "Whether to compress SAML response messages when Casdoor is used as SAML idp",
"Enable WebAuthn signin": "Enable WebAuthn signin",
"Enable WebAuthn signin - Tooltip": "Whether to allow users to login with WebAuthn",
"Enable code signin": "Enable code signin",
"Enable code signin - Tooltip": "Whether to allow users to login with phone or Email verification code",
"Enable password": "Enable password",
"Enable password - Tooltip": "Whether to allow users to login with password",
"Enable side panel": "Enable side panel",
"Enable signin session - Tooltip": "Whether Casdoor maintains a session after logging into Casdoor from the application",
"Enable signup": "Enable signup",
@ -100,6 +94,8 @@
"Sign Up Error": "Sign Up Error",
"Signin": "Signin",
"Signin (Default True)": "Signin (Default True)",
"Signin methods": "Signin methods",
"Signin methods - Tooltip": "Signin methods - Tooltip",
"Signin session": "Signin session",
"Signup items": "Signup items",
"Signup items - Tooltip": "Items for users to fill in when registering new accounts",
@ -107,6 +103,8 @@
"The application does not allow to sign up new account": "The application does not allow to sign up new account",
"Token expire": "Token expire",
"Token expire - Tooltip": "Access token expiration time",
"Token fields": "Token fields",
"Token fields - Tooltip": "The user fields included in the token",
"Token format": "Token format",
"Token format - Tooltip": "The format of access token",
"You are unexpected to see this prompt page": "You are unexpected to see this prompt page"
@ -173,6 +171,7 @@
"Admin": "Admin",
"Affiliation URL": "Affiliation URL",
"Affiliation URL - Tooltip": "The homepage URL for the affiliation",
"All": "All",
"Application": "Application",
"Application - Tooltip": "Application - Tooltip",
"Applications": "Applications",
@ -214,6 +213,7 @@
"Edit": "Edit",
"Email": "Email",
"Email - Tooltip": "Valid email address",
"Email only": "Email only",
"Enable": "Enable",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
@ -242,6 +242,7 @@
"ID": "ID",
"ID - Tooltip": "Unique random string",
"Identity": "Identity",
"Invitations": "Invitations",
"Is enabled": "Is enabled",
"Is enabled - Tooltip": "Set whether it can use",
"LDAPs": "LDAPs",
@ -266,6 +267,8 @@
"Models": "Models",
"Name": "Name",
"Name - Tooltip": "Unique, string-based ID",
"Name format": "Name format",
"Non-LDAP": "Non-LDAP",
"None": "None",
"OAuth providers": "OAuth providers",
"OK": "OK",
@ -287,6 +290,7 @@
"Permissions - Tooltip": "Permissions owned by this user",
"Phone": "Phone",
"Phone - Tooltip": "Phone number",
"Phone only": "Phone only",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Plans",
@ -383,6 +387,16 @@
"Past 30 Days": "Past 30 Days",
"Total users": "Total users"
},
"invitation": {
"Code": "Code",
"Code - Tooltip": "Can be a single string as an invitation code, or a regular expression. All strings matching the regular expression are valid invitation codes",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",
"Quota - Tooltip": "The maximum number of users that can register using this invitation code",
"Used count": "Used count",
"Used count - Tooltip": "The number of times this invitation code has been used"
},
"ldap": {
"Admin": "Admin",
"Admin - Tooltip": "CN or ID of the LDAP server administrator",
@ -416,16 +430,23 @@
"login": {
"Auto sign in": "Auto sign in",
"Continue with": "Continue with",
"Email": "Email",
"Email or phone": "Email or phone",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "Forgot password?",
"LDAP": "LDAP",
"LDAP username, Email or phone": "LDAP username, Email or phone",
"Loading": "Loading",
"Logging out...": "Logging out...",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"No account?": "No account?",
"Or sign in with another account": "Or sign in with another account",
"Phone": "Phone",
"Please input your Email or Phone!": "Please input your Email or Phone!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
"Please input your Phone!": "Please input your Phone!",
"Please input your code!": "Please input your code!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "Please input your password!",
@ -439,6 +460,8 @@
"Signing in...": "Signing in...",
"Successfully logged in with WebAuthn credentials": "Successfully logged in with WebAuthn credentials",
"The input is not valid Email or phone number!": "The input is not valid Email or phone number!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
"To access": "To access",
"Verification code": "Verification code",
"WebAuthn": "WebAuthn",
@ -751,11 +774,14 @@
"Region endpoint for Internet": "Region endpoint for Internet",
"Region endpoint for Intranet": "Region endpoint for Intranet",
"Required": "Required",
"Reset to Default HTML": "Reset to Default HTML",
"Reset to Default Text": "Reset to Default Text",
"SAML 2.0 Endpoint (HTTP)": "SAML 2.0 Endpoint (HTTP)",
"SMS Test": "SMS Test",
"SMS Test - Tooltip": "Phone number for sending test SMS",
"SMS account": "SMS account",
"SMS account - Tooltip": "SMS account",
"SMTP connected successfully": "SMTP connected successfully",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL",
"SP Entity ID": "SP Entity ID",
@ -862,6 +888,7 @@
"Please input your real name!": "Please input your real name!",
"Please select your country code!": "Please select your country code!",
"Please select your country/region!": "Please select your country/region!",
"Regex": "Regex",
"Terms of Use": "Terms of Use",
"Terms of Use - Tooltip": "Terms of use that users need to read and agree to during registration",
"Text 1": "Text 1",
@ -1028,8 +1055,6 @@
"Managed accounts": "Managed accounts",
"Modify password...": "Modify password...",
"Multi-factor authentication": "Multi-factor authentication",
"Name": "Name",
"Name format": "Name format",
"New Email": "New Email",
"New Password": "New Password",
"New User": "New User",
@ -1067,7 +1092,6 @@
"Upload ID card front picture": "Upload ID card front picture",
"Upload ID card with person picture": "Upload ID card with person picture",
"Upload a photo": "Upload a photo",
"Value": "Value",
"Values": "Values",
"Verification code sent": "Verification code sent",
"WebAuthn credentials": "WebAuthn credentials",

View File

@ -36,12 +36,6 @@
"Enable SAML C14N10 - Tooltip": "Enable SAML C14N10 - Tooltip",
"Enable SAML compression": "Activar la compresión SAML",
"Enable SAML compression - Tooltip": "Si comprimir o no los mensajes de respuesta SAML cuando se utiliza Casdoor como proveedor de identidad SAML",
"Enable WebAuthn signin": "Permite iniciar sesión con WebAuthn",
"Enable WebAuthn signin - Tooltip": "Si permitir a los usuarios iniciar sesión con WebAuthn",
"Enable code signin": "Habilitar la firma de código",
"Enable code signin - Tooltip": "Si permitir que los usuarios inicien sesión con código de verificación de teléfono o correo electrónico",
"Enable password": "Habilitar contraseña",
"Enable password - Tooltip": "Si permitir que los usuarios inicien sesión con contraseña",
"Enable side panel": "Habilitar panel lateral",
"Enable signin session - Tooltip": "Si Casdoor mantiene una sesión después de iniciar sesión en Casdoor desde la aplicación",
"Enable signup": "Habilitar registro",
@ -100,6 +94,8 @@
"Sign Up Error": "Error de registro",
"Signin": "Signin",
"Signin (Default True)": "Signin (Default True)",
"Signin methods": "Signin methods",
"Signin methods - Tooltip": "Signin methods - Tooltip",
"Signin session": "Sesión de inicio de sesión",
"Signup items": "Artículos de registro",
"Signup items - Tooltip": "Elementos para que los usuarios los completen al registrar nuevas cuentas",
@ -107,6 +103,8 @@
"The application does not allow to sign up new account": "La aplicación no permite registrarse una cuenta nueva",
"Token expire": "Token expirado",
"Token expire - Tooltip": "Tiempo de expiración del token de acceso",
"Token fields": "Token fields",
"Token fields - Tooltip": "Token fields - Tooltip",
"Token format": "Formato del token",
"Token format - Tooltip": "El formato del token de acceso",
"You are unexpected to see this prompt page": "Es inesperado ver esta página de inicio"
@ -173,6 +171,7 @@
"Admin": "Admin",
"Affiliation URL": "URL de afiliación",
"Affiliation URL - Tooltip": "La URL de la página de inicio para la afiliación",
"All": "All",
"Application": "Aplicación",
"Application - Tooltip": "Application - Tooltip",
"Applications": "Aplicaciones",
@ -214,6 +213,7 @@
"Edit": "Editar",
"Email": "Correo electrónico",
"Email - Tooltip": "Dirección de correo electrónico válida",
"Email only": "Email only",
"Enable": "Enable",
"Enabled": "Enabled",
"Enabled successfully": "Enabled successfully",
@ -242,6 +242,7 @@
"ID": "identificación",
"ID - Tooltip": "Cadena aleatoria única",
"Identity": "Identity",
"Invitations": "Invitations",
"Is enabled": "Está habilitado",
"Is enabled - Tooltip": "Establecer si se puede usar",
"LDAPs": "LDAPs (Secure LDAP)",
@ -266,6 +267,8 @@
"Models": "Modelos",
"Name": "Nombre",
"Name - Tooltip": "ID único basado en cadenas",
"Name format": "Name format",
"Non-LDAP": "Non-LDAP",
"None": "None",
"OAuth providers": "Proveedores de OAuth",
"OK": "Vale",
@ -287,6 +290,7 @@
"Permissions - Tooltip": "Permisos propiedad de este usuario",
"Phone": "Teléfono",
"Phone - Tooltip": "Número de teléfono",
"Phone only": "Phone only",
"Plan": "Plan",
"Plan - Tooltip": "Plan - Tooltip",
"Plans": "Planes",
@ -383,6 +387,16 @@
"Past 30 Days": "Past 30 Days",
"Total users": "Total users"
},
"invitation": {
"Code": "Code",
"Code - Tooltip": "Code - Tooltip",
"Edit Invitation": "Edit Invitation",
"New Invitation": "New Invitation",
"Quota": "Quota",
"Quota - Tooltip": "Quota - Tooltip",
"Used count": "Used count",
"Used count - Tooltip": "Used count - Tooltip"
},
"ldap": {
"Admin": "Administrador",
"Admin - Tooltip": "CN o ID del administrador del servidor LDAP",
@ -416,16 +430,23 @@
"login": {
"Auto sign in": "Inicio de sesión automático",
"Continue with": "Continúe con",
"Email": "Email",
"Email or phone": "Correo electrónico o teléfono",
"Failed to obtain MetaMask authorization": "Failed to obtain MetaMask authorization",
"Failed to obtain Web3-Onboard authorization": "Failed to obtain Web3-Onboard authorization",
"Forgot password?": "¿Olvidaste tu contraseña?",
"LDAP": "LDAP",
"LDAP username, Email or phone": "LDAP username, Email or phone",
"Loading": "Cargando",
"Logging out...": "Cerrando sesión...",
"MetaMask plugin not detected": "MetaMask plugin not detected",
"No account?": "¿No tienes cuenta?",
"Or sign in with another account": "O inicia sesión con otra cuenta",
"Phone": "Phone",
"Please input your Email or Phone!": "¡Por favor introduzca su correo electrónico o teléfono!",
"Please input your Email!": "Please input your Email!",
"Please input your LDAP username!": "Please input your LDAP username!",
"Please input your Phone!": "Please input your Phone!",
"Please input your code!": "¡Por favor ingrese su código!",
"Please input your organization name!": "Please input your organization name!",
"Please input your password!": "¡Ingrese su contraseña, por favor!",
@ -439,6 +460,8 @@
"Signing in...": "Iniciando sesión...",
"Successfully logged in with WebAuthn credentials": "Inició sesión correctamente con las credenciales de WebAuthn",
"The input is not valid Email or phone number!": "¡La entrada no es un correo electrónico o número de teléfono válido!",
"The input is not valid Email!": "The input is not valid Email!",
"The input is not valid phone number!": "The input is not valid phone number!",
"To access": "para acceder",
"Verification code": "Código de verificación",
"WebAuthn": "WebAuthn (Autenticación Web)",
@ -751,11 +774,14 @@
"Region endpoint for Internet": "Punto final de la región para Internet",
"Region endpoint for Intranet": "Punto final de región para Intranet",
"Required": "Requerido",
"Reset to Default HTML": "Reset to Default HTML",
"Reset to Default Text": "Reset to Default Text",
"SAML 2.0 Endpoint (HTTP)": "Punto final de SAML 2.0 (HTTP)",
"SMS Test": "Prueba de SMS",
"SMS Test - Tooltip": "Número de teléfono para enviar mensajes de texto de prueba",
"SMS account": "Cuenta de SMS",
"SMS account - Tooltip": "Cuenta de SMS",
"SMTP connected successfully": "SMTP connected successfully",
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "URL del ACS de SP",
"SP Entity ID": "ID de entidad SP",
@ -862,6 +888,7 @@
"Please input your real name!": "¡Por favor, ingresa tu nombre real!",
"Please select your country code!": "¡Por favor seleccione su código de país!",
"Please select your country/region!": "¡Por favor seleccione su país/región!",
"Regex": "Regex",
"Terms of Use": "Términos de uso",
"Terms of Use - Tooltip": "Términos de uso que los usuarios necesitan leer y aceptar durante el registro",
"Text 1": "Text 1",
@ -1028,8 +1055,6 @@
"Managed accounts": "Cuentas gestionadas",
"Modify password...": "Modificar contraseña...",
"Multi-factor authentication": "Multi-factor authentication",
"Name": "Name",
"Name format": "Name format",
"New Email": "Nuevo correo electrónico",
"New Password": "Nueva contraseña",
"New User": "Nuevo Usuario",
@ -1067,7 +1092,6 @@
"Upload ID card front picture": "Upload ID card front picture",
"Upload ID card with person picture": "Upload ID card with person picture",
"Upload a photo": "Subir una foto",
"Value": "Value",
"Values": "Valores",
"Verification code sent": "Código de verificación enviado",
"WebAuthn credentials": "Credenciales de WebAuthn",

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