Compare commits

...

231 Commits

Author SHA1 Message Date
498cd02d49 feat: add GetAllowedApplications() in user's app homepage 2023-11-08 09:48:31 +08:00
a389842f59 Improve Product fields 2023-11-06 19:44:21 +08:00
6c69daa666 feat: fix search for ldap users' name within an organization (#2476)
* fix: #2304

* fix: when logging in with OAuth2 and authenticating via WebAuthn, retrieve the application from the clientId.

* fix: search for ldap users' name within an organization

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-11-06 11:48:23 +08:00
53c89bbe89 feat: upgrade xorm-adapter to add id to CasbinRule 2023-11-03 02:48:01 +08:00
9442aa9f7a Remove useless PermissionRule 2023-11-03 00:39:16 +08:00
8a195715d0 Remove migrator code 2023-11-03 00:25:09 +08:00
b985bab3f3 fix: fix dropped errors in GetUser() (#2470)
* controllers: fix dropped errors

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-11-01 23:07:24 +08:00
477a090aa0 feat: when logging in with OAuth2 and authenticating via WebAuthn, retrieve the application from the clientId (#2469)
* fix: #2304

* fix: when logging in with OAuth2 and authenticating via WebAuthn, retrieve the application from the clientId.

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-11-01 18:40:05 +08:00
e082cf10e0 fix: fix Okta provider no host issue (#2467) 2023-11-01 18:14:39 +08:00
3215b88eae fix: ADFS GetToken() and GetUserInfo() bug (#2468)
* fix adfs bug

* Update adfs.go

---------

Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
2023-11-01 17:58:17 +08:00
9703f3f712 Support Apple OAuth login now 2023-10-31 23:10:36 +08:00
140737b2f6 Fix some bugs in Apple OAuth login path 2023-10-31 23:10:36 +08:00
b285144a64 ci: support MySQL data sync (#2443)
* feat: support tool for mysql master-slave sync

* feat: support mysql master-master sync

* feat: improve log

* feat: improve code

* fix: fix bug when len(res) ==0

* fix: fix bug when len(res) ==0

* feat: support master-slave sync

* feat: add deleteSlaveUser for TestStopMasterSlaveSync

* feat: add deleteSlaveUser for TestStopMasterSlaveSync
2023-10-31 21:00:09 +08:00
49c6ce2221 refactor: New Crowdin translations (#1667)
* refactor: New Crowdin translations by Github Action

* refactor: New Crowdin Backend translations by Github Action

---------

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-10-31 18:11:05 +08:00
2398e69012 Improve fastAutoSignin() 2023-10-31 16:54:30 +08:00
ade9de8256 Add DumpToFile() to export init_data.json 2023-10-31 14:39:50 +08:00
1bf5497d08 Improve error handling for GetUser() 2023-10-31 14:01:37 +08:00
cf10738f45 Fix typo in AddUserKeys() 2023-10-31 13:31:12 +08:00
ac00713c20 Improve error handling for object/user.go 2023-10-31 13:20:44 +08:00
febb27f765 Remove useless fields in GenerateCasToken() 2023-10-30 18:45:34 +08:00
49a981f787 fix: fix that GROUPS is a reserved keyword introduced in MySQL 8.0 (#2458)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-30 10:59:48 +08:00
34b1945180 feat: fix bugs in custom app sso login with WebAuthn authentication (#2457)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-30 10:54:34 +08:00
b320cca789 Can disable ldapServerPort by setting to empty string 2023-10-29 23:55:08 +08:00
b38654a45a Add renderAiAssistant() 2023-10-28 23:58:51 +08:00
f77fafae24 Fix hidden top navbar item 2023-10-28 17:07:29 +08:00
8b6b5ffe81 feat: fix go-reddit module checksum mismatch (#2451) 2023-10-28 15:32:36 +08:00
a147fa3e0b feat: fix bug that tableNamePrefix caused getRolesByUserInternal() to fail (#2450)
If set tableNamePrefix in app.conf, while cause sql error
2023-10-28 09:45:54 +08:00
9d03665523 Fix FromProviderToIdpInfo() bug 2023-10-27 18:10:22 +08:00
0106c7f7fa Fix GetIdProvider() bug 2023-10-27 17:03:37 +08:00
6713dad0af Fix this.props.account null issue 2023-10-27 02:13:23 +08:00
6ef2b51782 Support fastAutoSignin by backend redirection 2023-10-27 00:44:50 +08:00
1732cd8538 Fix the bug that sometimes cannot auto login with enableAutoSignin = true 2023-10-27 00:06:17 +08:00
a10548fe73 Fix org admin's enforcer policy APIs 2023-10-26 23:31:36 +08:00
f6a7888f83 Deleted user cannot perform actions 2023-10-26 10:41:38 +08:00
93efaa5459 Fix FileExist() error handling 2023-10-26 10:40:28 +08:00
0bfe683108 feat: change canonicalizer algorithm to xml-exc-c14n# (#2440) 2023-10-24 14:13:09 +08:00
8a4758c22d Update sync code 2023-10-22 11:56:56 +08:00
ee3b46e91c Allow permission.Model to be empty 2023-10-22 02:35:51 +08:00
37744d6cd7 Improve permission error handling 2023-10-22 02:30:29 +08:00
98defe617b Add providerItem.SignupGroup 2023-10-20 23:10:43 +08:00
96cbf51ca0 Remove useless alertType field 2023-10-20 23:01:11 +08:00
22b57fdd23 Add application.EnableSamlC14n10 2023-10-20 22:37:23 +08:00
b68e291f37 feat: support SAML Custom provider (#2430)
* 111

* feat: support custom saml provider

* feat: gofumpt code

* feat: gofumpt code

* feat: remove comment

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-10-20 21:11:36 +08:00
9960b4933b feat: respect isReadOnly in the syncer (#2427)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-19 18:57:12 +08:00
432a5496f2 fix: skip checking password when the code is provided (#2425)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-19 18:25:25 +08:00
45db4deb6b feat: support checking permissions for group roles (#2422)
* fix(permission): fix CheckLoginPermission() logic

* style: fix code format

* feat: support settting roles for groups

* fix: fix field name

* style: format codes

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-19 15:33:45 +08:00
3f53591751 Improve verification no provider error message 2023-10-18 15:32:12 +08:00
d7569684f6 Local admin can edit its org user's other fields now 2023-10-18 12:16:05 +08:00
a616127909 Add organization.DefaultPassword 2023-10-18 11:58:25 +08:00
f2e2b960ff Improve downloadImage() error handling 2023-10-18 02:25:22 +08:00
fbc603876f feat: add originFrontend to app.conf 2023-10-17 21:47:18 +08:00
9ea77c63d1 Local admin can edit its org users now 2023-10-17 18:23:39 +08:00
53243a30f3 feat: support tencent cloud SAML SSO authentication with casdoor (#2409)
* feat: Support Tencent Cloud SAML SSO authentication with Casdoor

* feat: support SamlAttributeTable in the frontend

* fix:fixed the error where frontend fields did not match the database fields

* fix:fix lint error

* fix:fixed non-standard naming

* fix:remove if conditional statement

* feat:Add Saml Attribute format select

* fix:fix typo

* fix:fix typo

* fix:fix typo

* Update SamlAttributeTable.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-10-17 15:40:41 +08:00
cbdeb91ee8 feat: support groups in app login permissions (#2413)
* fix(permission): fix CheckLoginPermission() logic

* style: fix code format

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-10-17 14:35:13 +08:00
2dd1dc582f Add text to app's signup table 2023-10-15 18:17:50 +08:00
f3d4b45a0f Add label and placeholder to app's signup table 2023-10-15 17:24:38 +08:00
2ee4aebd96 Fix error handling in GetSamlMeta() 2023-10-15 17:02:40 +08:00
150e3e30d5 Support app user in API authentication 2023-10-15 15:20:57 +08:00
1055d7781b Improve error handling in AutoSigninFilter 2023-10-15 12:43:36 +08:00
1c296e9b6f feat: activate enableGzip by default in app.conf 2023-10-15 01:27:42 +08:00
3d80ec721f fix: use user.UpdatedTime as scim.Meta.Version instead of user.Id (#2406)
* 111

* fix: use user.UpdatedTime as scim.Meta.Version instead of user.Id

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-10-14 11:03:58 +08:00
43d849086f Fix 127.0.0.1 bug in isHostIntranet() 2023-10-13 23:29:37 +08:00
69b144d80f feat: change back to running RecordMessage() filter before API handling, because the logged-out user info is missing after session is cleared. Revert: https://github.com/casdoor/casdoor/pull/2369 2023-10-13 16:53:30 +08:00
52a66ef044 Fix webhook not triggered issue in SendWebhooks() 2023-10-13 16:47:09 +08:00
ec0a8e16f7 feat: fix CheckLoginPermission() logic 2023-10-13 15:41:23 +08:00
80a8000057 Add GetModelEx() 2023-10-13 13:45:13 +08:00
77091a3ae5 Fix null model issue in UpdatePermission() 2023-10-13 12:55:11 +08:00
983da685a2 feat: support calling get-user API by only email, phone or userId without owner (#2398) 2023-10-13 02:48:55 +08:00
3d567c3d45 feat: update go-sms-sender to fix Twilio template error (#2395) 2023-10-12 01:53:31 +08:00
440d87d70c feat: support SCIM protocol (#2393)
* 111

* feat: support scim/Users GET and POST request

* feat: support scim/Users DELETE/PATCH/PUT request

* feat: better support scim/Users PATCH request

* feat: fix scim/Users logic

* feat: gofumpt

* feat: fix bug in scim/Users

* feat: fix typo

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-10-12 00:13:16 +08:00
e4208d7fd9 feat: restrict the model of application type resource permission (#2394) 2023-10-12 00:05:53 +08:00
4de716fef3 Improve UploadResource() 2023-10-11 01:27:29 +08:00
070aa8a65f Show 404 error for index.html not found 2023-10-10 22:57:39 +08:00
wxy
684cbdb951 fix: replace the wrong param name willExist (#2389) 2023-10-10 21:47:38 +08:00
9aec69ef47 feat: stop building docker image of linux/arm64 (#2390) 2023-10-10 21:19:54 +08:00
98411ef67b feat: remove db migrate CI 2023-10-10 19:22:41 +08:00
71279f548d Show cert.Certificate empty error 2023-10-10 19:19:20 +08:00
0096e47351 feat: fix 403 error in CorsFilter 2023-10-10 18:39:25 +08:00
814d3f749b Fix Syncer.getKey() 2023-10-09 02:47:42 +08:00
ec0f457c7f Fix syncer.updateUser() bug 2023-10-09 01:14:35 +08:00
0033ae1ff1 Improve syncer code 2023-10-08 20:50:28 +08:00
d06d7c5c09 Fix batch methods like AddUsersInBatch() 2023-10-08 19:33:28 +08:00
23c4fd8183 Fix go-reddit v2.0.1 doesn't exist issue 2023-10-08 19:29:26 +08:00
e3558894c3 Add isHostIntranet to CORS filter 2023-10-08 19:29:19 +08:00
2fd2d88d20 Return 403 in filter's responseError() 2023-10-05 00:12:02 +08:00
d0c424db0a Don't panic in AddRecord() 2023-10-05 00:11:13 +08:00
6a9d1e0fe5 Add frontendBaseDir 2023-10-04 12:19:56 +08:00
938e8e2699 Improve code 2023-09-30 10:49:10 +08:00
620383cf33 Allow CORS for https://localhost 2023-09-30 09:11:47 +08:00
de6cd380eb Set OPTIONS status in setCorsHeaders() 2023-09-30 01:13:29 +08:00
7e0bce2d0f feat: run RecordMessage() filter after API handling (#2369)
* feat: write records after exec (#2368)

* add returnOnOutput params
2023-09-29 10:12:00 +08:00
1461268a51 Allow redirect URL for casdoor-app 2023-09-27 22:37:57 +08:00
5ec49dc883 feat: fix claims.tag and UserWithoutThirdIdp missing fields, fix for Rust SDK 2023-09-27 18:07:57 +08:00
5c89705d9e feat: allow CORS for 127.0.0.1 2023-09-27 14:10:59 +08:00
06e3b8481f Improve adapter error handling 2023-09-27 01:11:58 +08:00
81a8b91e3f Fix enforcer policy add and delete 2023-09-27 00:18:21 +08:00
56787fab90 Improve adapter.UseSameDb 2023-09-26 23:41:09 +08:00
1319216625 Add adapter.UseSameDb 2023-09-26 23:41:08 +08:00
6fe5c44c1c feat: support radius accounting request (#2362)
* feat: add radius server

* feat: parse org from packet

* feat: add comment

* feat: support radius accounting

* feat: change log

* feat: add copyright
2023-09-26 22:48:00 +08:00
981908b0b6 Fix crash in LDAP's sync: GenerateIdForNewUser() 2023-09-26 19:12:28 +08:00
03a281cb5d Improve CorsFilter code 2023-09-26 14:51:38 +08:00
a8e541159b Allow localhost in CorsFilter 2023-09-26 00:03:26 +08:00
577bf91d25 Refactor out setCorsHeaders() 2023-09-26 00:02:31 +08:00
329a6a8132 Fix get-pricing and get-plan API null error handling 2023-09-25 22:11:08 +08:00
fba0866cd6 Fix error handling in StartRadiusServer() 2023-09-25 20:55:02 +08:00
aab6a799fe fix: use client secret field for providers (#2355)
* feat: fix key exposure problem

* fix display bug
2023-09-24 18:35:58 +08:00
b94d06fb07 feat: add some Radius protocol code (#2351)
* feat: add radius server

* feat: parse org from packet

* feat: add comment

* Update main.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-09-24 16:50:31 +08:00
f9cc6ed064 Add groups to role 2023-09-24 10:17:18 +08:00
4cc9137637 Improve permission, adapter page UI 2023-09-24 09:56:06 +08:00
d145ab780c feat: fix wrong elements in getPermissionsByUser() related functions 2023-09-24 09:13:54 +08:00
687830697e Refactor getPermissionsAndRolesByUser() related code 2023-09-24 08:08:32 +08:00
111d1a5786 Use UserInfo's ID in OAuth login 2023-09-23 00:13:13 +08:00
775dd9eb57 Improve email provider error handling and fix bug 2023-09-21 23:11:58 +08:00
8f6c295c40 fix: empty AzureAD tenant id (#2349) 2023-09-21 08:34:23 +08:00
2f31e35315 feat: update casbin to 2.77.2 (#2345)
* fix: make redirect_uri really optional in logout route

* feat: update casbin to 2.77.2
2023-09-20 23:37:55 +08:00
b6d6aa9d04 Use GenerateIdForNewUser() in add-user API 2023-09-20 22:50:17 +08:00
f40d44fa1c Refactor out GenerateIdForNewUser() 2023-09-20 22:45:00 +08:00
3b2820cbe3 feat: make redirect_uri really optional in logout route (#2342) 2023-09-18 21:47:49 +08:00
764e88f603 Change MFA issuer 2023-09-18 17:40:11 +08:00
7f298efebc feat: fix Apple OAuth issue (#2338)
* feat: fix sign in with apple bug

* fix username
2023-09-18 17:04:03 +08:00
0fc48bb6cd Remove escapePath() to fix Unicode resource filenames 2023-09-17 21:31:22 +08:00
c3b3840994 fix: fix update score permission check (#2335)
* fix: Fixed the missing permission check when updating the score field.
* Update object/user_util.go
2023-09-16 21:06:20 +08:00
eacc3fae5a fix: handle more errors in downloadImage() 2023-09-15 22:53:09 +08:00
ce7a2e924b feat: fix XML format issue in GenerateCasToken() 2023-09-15 22:38:04 +08:00
ece060d03d feat: fix XML bug in GenerateCasToken() 2023-09-15 18:57:59 +08:00
1276da4daa Check old password for normal user in SetPassword() 2023-09-15 10:21:02 +08:00
616629ef99 Refactor CheckLoginPermission() code 2023-09-15 02:47:53 +08:00
b633ecdcf2 Fix bug that cannot access application's public certificate for non "admin" owner 2023-09-15 00:56:40 +08:00
a12ba7fb85 feat: allow CORS for UserInfo API in OIDC (#2313) 2023-09-13 18:11:13 +08:00
08a0092974 feat: fix alipay payment provider (#2330)
* feat: support alipay payment provider

* feat: update notify params

* feat: update root cert

* feat: update ProviderEditPage.js

* feat: gofumpt
2023-09-13 17:30:51 +08:00
bb04b10e8b Fix JSON issue in GenerateCasToken() 2023-09-13 16:45:11 +08:00
ea1414dfd0 Fix typo 2023-09-13 00:19:32 +08:00
32a8a028d5 Set TOTP issuer to Casdoor 2023-09-12 23:56:39 +08:00
0fe34c2f53 Fix the issue that database syncer can't work with null-fields on source table 2023-09-12 16:06:44 +08:00
dc57c476b7 feat: support acs email provider (#2323)
* feat: support acs email provider

* feat: support acs email provider

* hide Test SMTP Connection button

* fix name acs
2023-09-12 02:13:37 +08:00
a7cb202ee9 feat: fix JSON tag of EmailVerified (#2322)
Signed-off-by: Cattī Crūdēlēs <17695588+wzy9607@users.noreply.github.com>
2023-09-11 18:33:24 +08:00
e5e264628e Remove "RUN mkdir tempFiles" 2023-09-09 20:24:18 +08:00
8d4127f744 feat: improve dashboard UI for mobile devices (#2320) 2023-09-09 16:17:24 +08:00
1305899060 Fix "app" user API denied issue 2023-09-09 15:44:36 +08:00
411a85c7ab Remove useless GetMaxLenStr() 2023-09-09 15:40:35 +08:00
f39358e122 Improve SMS Test's initial value 2023-09-09 02:38:15 +08:00
a84752bbb5 Update go-sms-sender to v0.14.0 2023-09-09 02:15:38 +08:00
e9d8ab8cdb fix: hide tour component for mobile (#2317) 2023-09-08 22:53:46 +08:00
d12088e8e7 feat: fix bug in pricing when signup by phone (#2316)
* fix: fix bug in pricing

* fix: remove log
2023-09-08 21:03:30 +08:00
c62588f9bc Add EmailVerified to UserInfo 2023-09-08 18:27:14 +08:00
16cd09d175 feat: support wechat pay (#2312)
* feat: support wechat pay

* feat: support wechat pay

* feat: update wechatpay.go

* feat: add router /qrcode
2023-09-07 15:45:54 +08:00
7318ee6e3a Improve LocalFileSystemProvider's error handling 2023-09-07 10:49:39 +08:00
3459ef1479 Improve termsOfUse UI and error handling 2023-09-07 10:33:20 +08:00
ca6b27f922 feat: fix notification provider frontend bug and twitter error (#2310) 2023-09-06 23:41:34 +08:00
e528e8883b Add "localhost" to IsRedirectUriValid() 2023-09-06 21:14:58 +08:00
b7cd604e56 Mask user in GenerateCasToken() 2023-09-06 18:36:55 +08:00
3c2fd574a6 Refactor GenerateCasToken() 2023-09-06 18:35:13 +08:00
a9de7d3aef Add groups to permission 2023-09-06 00:10:33 +08:00
9820801634 Make Product's Providers longer (255) 2023-09-05 20:24:24 +08:00
c6e422c3a8 feat: add multiple notification providers (#2302)
* feat: support dingtalk notification provider

* feat: support lark notification provider

* feat: support microsoft teams notification provider

* feat: support bark notification provider

* feat: support pushover notification provider

* feat: support pushbullet notification provider

* feat: support slack notification provider

* feat: support webpush notification provider

* fix go-test error

* update notify repository

* feat: support discord notification provider

* feat: support google chat notification provider

* feat: support Line notification provider

* feat: support matrix notification provider

* feat: support twitter notification provider

* fix lint

* add no proxy provider

* update setting.js

* update social_teams
2023-09-05 17:05:34 +08:00
bc8e9cfd64 feat: storage provider's domain initial value bug (#2303) 2023-09-05 14:53:32 +08:00
c1eae9fcd8 Fix TotpMfa's Verify() 2023-09-04 19:21:26 +08:00
6dae6e4954 docs: fix all dead links (#2297)
https://github.com/Selflocking/linkchecker/actions/runs/6058177987
2023-09-03 21:19:23 +08:00
559a91e8ee feat: fix bug that failed to set password after changing username (#2296)
* fix: failed to set password after changing username

When we add a new member to an organization using Casdoor, Casdoor will automatically generate a member with a random username, such as "user_qvducc". When we change the username, for example, to "yunshu", an issue arises where we are unable to successfully edit the password. This is because Casdoor searches for a user based on `owner/username`, and before any changes are saved, the username in the database remains "user_qvducc". However, the frontend uses `orgName/yunshu` instead of `orgName/user_qvducc` to send the request to change the password. As a result, the backend cannot find the user and the password change fails.

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-09-03 00:04:48 +08:00
b0aaf09ef1 Add 7 new i18n languages 2023-09-02 18:49:43 +08:00
7e2f67c49a Fix i18n error 2023-09-02 18:33:19 +08:00
e584a6a111 Support using "?allowEmpty=1" to bypass empty displayName check in update-user API 2023-09-02 11:59:07 +08:00
6700d2e244 fix: show error when frontend HTML entry does not exist (#2289)
* fix: add response when web file not found

The error flow is as follows:

Assuming my directory structure is as follows:

```tree
├── GitHub
│   ├── casdoor  # code repository
              ├── casdoor # compiled binary file
```

Execute the program in the `GitHub` directory:

```bash
./casdoor/casdoor
```

The working directory at this time is `GitHub`.

According to the code:

```go
func StaticFilter(ctx *context.Context) {
	urlPath := ctx.Request.URL.Path

   /// omitted

	path := "web/build"
	if urlPath == "/" {
		path += "/index.html"
	} else {
		path += urlPath
	}

	if !util.FileExist(path) {
		path = "web/build/index.html"
	}
	if !util.FileExist(path) {
		return
	}

    /// omitted
}
```

If the user accesses `/`, according to this code, the returned value is actually `web/build/index.html`. But the current directory is GitHub, and there is no `web/build/index.html` file. According to the following code, it will directly return:

```go
	if !util.FileExist(path) {
		return
	}
```

Then in `main.go`:

```go
	beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
	beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
	beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
	beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
	beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
	beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
```

The introduction of `beego.InsertFilter` is as follows:

```
func InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) *App

InsertFilter adds a FilterFunc with pattern condition and action constant. The pos means action constant including beego.BeforeStatic, beego.BeforeRouter, beego.BeforeExec, beego.AfterExec and beego.FinishRouter. The bool params is for setting the returnOnOutput value (false allows multiple filters to execute)
```

When the `params` parameter is `false`, it runs multiple filters. The default is `true`.

So normally, if

```go
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
```

response something, the following filters will not be executed. But because the file does not exist, the function directly returns, causing the subsequent filters to continue executing. When it reaches

```go
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
```

it will start to check permissions:

```
subOwner = anonymous, subName = anonymous, method = GET, urlPath = /login, obj.Owner = , obj.Name = , result = deny
```

Then it will report this error:

```json
{
    "status": "error",
    "msg": "Unauthorized operation",
    "data": null,
    "data2": null
}
```

The solution should be:

```go
func StaticFilter(ctx *context.Context) {
	urlPath := ctx.Request.URL.Path

   /// omitted

	path := "web/build"
	if urlPath == "/" {
		path += "/index.html"
	} else {
		path += urlPath
	}

	if !util.FileExist(path) {
		// todo: response error: page not found
		return
	}

    /// omitted
}
```

* Update static_filter.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-09-02 00:06:04 +08:00
0c5c308071 fix: sendCasAuthenticationResponseErr when pgtUrlObj if not valid url (#2287)
* fix: sendCasAuthenticationResponseErr when pgtUrlObj if not valid url

check pgtUrlObj.Scheme first will cause panic if url.Parse returns error.

* Update cas.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-09-01 22:26:57 +08:00
0b859197da Fix CAS "/proxyValidate" API 2023-09-01 21:47:26 +08:00
3078409343 Add CertPublicKey to Application 2023-09-01 21:16:51 +08:00
bbf2db2e00 feat: support to use a different db schema for pg (#2281) 2023-09-01 18:02:13 +08:00
0c7b911ce7 Fix enforcer edit page logic 2023-09-01 01:30:50 +08:00
2cc55715ac Add app.conf existence check 2023-09-01 01:25:45 +08:00
c829bf1769 Fix DummyPaymentProvider's return URL 2023-09-01 01:25:15 +08:00
ec956c12ca Fix Email duplicated issue in update-user 2023-08-31 23:44:40 +08:00
d3d4646c56 feat: fix can not create db when using pg with a dbname in DSN (#2280)
* fix: can not create db when using pg with a dbname in DSN

* Update ormer.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-31 18:05:38 +08:00
669ac7c618 Don't encrypt user pass when user.PasswordType is non-empty when adding users 2023-08-31 17:49:36 +08:00
6715efd781 Fix enforcer edit page 2023-08-31 17:32:36 +08:00
953be4a7b6 feat: support subscription periods (yearly/monthly) (#2265)
* feat: support year/month subscription

* feat: add GetPrice() for plan

* feat: add GetDuration

* feat: gofumpt

* feat: add subscription mode for pricing

* feat: restrict auto create product operation

* fix: format code

* feat: add period for plan,remove period from pricing

* feat: format code

* feat: remove space

* feat: remove period in signup page
2023-08-30 17:13:45 +08:00
943cc43427 Fix payment list and product edit actions 2023-08-28 21:01:23 +08:00
1e5ce7a045 Fix crash in syncUsersNoError() 2023-08-28 01:51:06 +08:00
7a85b74573 fix: fix tour disabled state (#2264)
* fix: distinguish between pages that can tour or not

* Update OpenTour.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-27 23:18:14 +08:00
7e349c1768 feat: fix crash bug in getSteps() 2023-08-27 21:58:58 +08:00
b19be2df88 fix: change the id to key in syncer (#2263) 2023-08-27 20:57:27 +08:00
fc3866db1c Use XORM grammar in syncer 2023-08-27 18:15:23 +08:00
bf2bb31e41 Add sslMode for syncer 2023-08-27 17:07:19 +08:00
ec8bd6f01d feat: add tour for list pages (#2243) 2023-08-27 16:40:31 +08:00
98722fd681 Fix crash in app list page for normal user 2023-08-27 11:31:48 +08:00
221c55aa93 Fix yarn build cmd 2023-08-27 11:17:18 +08:00
988b26b3c2 Return error for RunSyncer() 2023-08-27 02:22:37 +08:00
7e3c361ce7 Add all webhook events 2023-08-26 23:50:24 +08:00
a637707e77 Fix null bug in IsAdminOrSelf() 2023-08-26 10:39:46 +08:00
7970edeaa7 feat: password and invitation code verification rules (#2258) 2023-08-25 21:16:21 +08:00
9da2f0775f fix: fix bug in Pricing (#2255) 2023-08-25 19:27:46 +08:00
739a9bcd0d feat: add CasvisorUrl 2023-08-25 11:56:12 +08:00
fb0949b9ed Fix docker cannot get version bug 2023-08-25 11:49:47 +08:00
27ed901167 Restrict sysinfo page to global admin 2023-08-25 11:20:11 +08:00
ceab662b88 Remove dup swagger page 2023-08-25 11:09:59 +08:00
05b2f00057 feat: support Pricings flow (#2250)
* feat: fix price display

* feat: support subscription

* feat: fix select-plan-> signup -> buy-plan -> login flow

* feat: support paid-user to login and jump to the pricing page

* feat: support more subscription state

* feat: add payment providers for plan

* feat: format code

* feat: gofumpt

* feat: redirect to buy-plan-result page when user have pending subscription

* feat: response err when pricing don't exit

* Update PricingListPage.js

* Update ProductBuyPage.js

* Update LoginPage.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-24 23:20:50 +08:00
8073dfa88c Remove tmpFiles folder usage 2023-08-24 22:03:36 +08:00
1eeeb64a0c Add checkModel() for UserGroupEnforcer 2023-08-24 18:22:23 +08:00
f5e0461cae feat: add invitation code for signup feature (#2249)
* feat: add invitation code for signup feature

* feat: add invitation code for signup feature
2023-08-24 13:42:17 +08:00
a0c5eb241f feat: add fields to syncer (PreferredMfaType, TotpSecret, SignupApplication) #2239 (#2245) 2023-08-23 21:40:00 +08:00
4d8edcc446 fix: dropped controllers err (#2244)
Signed-off-by: Lars Lehtonen <lars.lehtonen@gmail.com>
2023-08-23 21:37:51 +08:00
2b23c04f49 fix: add SignupApplication and type for user synced from LDAP (#2240) 2023-08-21 22:52:35 +08:00
e60ee52d91 feat: replace satori/go.uuid with google/uuid (#2238) 2023-08-21 13:58:15 +08:00
c54b54ca19 fix: Adjust custom http to notification provider (#2237)
* feat: Adjust custom http to notification provider

* fix go linter

* update ProviderEditPage

* update ProviderEditPage
2023-08-20 21:04:30 +08:00
f0e097e138 feat: fix home page (#2236)
* fix: home page

* fix: home page
2023-08-20 00:58:39 +08:00
25ec1bdfa8 Fix bug in getUserOrganization() 2023-08-20 00:53:51 +08:00
ea7718d7b7 Use Casvisor for records 2023-08-20 00:44:01 +08:00
463fa8b636 Add ormer_session.go 2023-08-19 18:41:08 +08:00
11895902f4 Move getCreateDatabaseFlag() to ormer 2023-08-19 16:44:34 +08:00
15269d3315 Refactor out conf_quota.go 2023-08-19 16:39:21 +08:00
4468859795 Improve sendTest msg 2023-08-19 12:47:51 +08:00
914128a78a fix: Support Telegram Notification provider (#2225)
* fear: support telegram provider

* fix: fix telegram logo

* fix: fix telegram bot package

* Update telegram.go

* Update notification.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-19 12:33:00 +08:00
e5a189e0f4 fix: remove isGlobalAdmin field in user (#2235)
* refactor: remove isGlobalAdmin field in user

* fix: upload xlsx

* fix: remove field in account table
2023-08-19 12:23:15 +08:00
a07216d0e1 Improve contentType parsing in downloadImage() 2023-08-19 02:35:45 +08:00
fec54944dd feat: fix CAS login bug (#2230)
* fix: cas login

* fix: cas login

* feat: rollback get-default-app change

* fix : move cas restrict logic to GetApplicationLogin()

* fix: format code

* fix: fix getOAuthGetParameters for cas

* fix: fix getOAuthGetParameters for cas

* fix: cas login
2023-08-19 01:15:41 +08:00
a2db61cc1a chore: Revert "feat: restrict redirectUrls for CAS login" (#2234)
This reverts commit b7a37126ad.
2023-08-19 00:30:35 +08:00
134541acde chore: put some dev dependency package to right place (#2232) 2023-08-18 22:17:16 +08:00
59fca0342e chore: fix yarn build warning (#2231) 2023-08-18 21:25:57 +08:00
abfc464155 Remove isEnabled for model, adapter and enforcer, improve UI 2023-08-18 19:22:47 +08:00
a41f6880a2 feat: move policy table from adapter to enforcer and improve it (#2228)
* feat: improve policiy table

* feat: add connection test in AdapterEditPage.js

* feat: update button style
2023-08-18 19:00:21 +08:00
d12117324c feat: support admin to enable MFA for other users (#2221)
* feat: support admin enable user sms and email mfa

* chore: update ci

* chore: update ci
2023-08-17 17:19:24 +08:00
1a6c9fbf69 Fix typo in README 2023-08-17 14:47:09 +08:00
dd60d79af9 Fix typo in README 2023-08-17 14:46:10 +08:00
73d314c7fe Add MfaTotpPeriodInSeconds param 2023-08-16 21:48:54 +08:00
27959e0f6f fix: fix crash in UserEditPage.js 2023-08-16 15:57:48 +08:00
47f40c5b24 feat: support 3 more UI languages (#2218)
Signed-off-by: baihhh <2542274498@qq.com>
2023-08-16 15:54:34 +08:00
2ff9020884 feat: support Stripe payment provider (#2204)
* feat: add stripe payment provider

* feat: support stripe payment

* feat: delete todo comment

* feat: remove description struct

* feat: change outOrderId->orderId
2023-08-15 00:16:30 +08:00
abaf4ca8d9 Make GetDashboard() faster 2023-08-14 15:43:09 +08:00
8ff0cfd6ec feat: support dashboard in homepage (#2207)
* feat: support dashboard

* feat: support dashboard
2023-08-14 15:31:29 +08:00
7a2a40edcc Improve table columns 2023-08-14 12:19:02 +08:00
b7a001ea39 Fix property empty issue 2023-08-14 12:09:50 +08:00
891e8e21d8 feat: support Web3-Onboard provider (#2209)
* feat: add Web3-Onboard idp

* feat: update Web3-Onboard logo

* feat: update package.json

* feat: remove unused package

* feat: add yarn build param --max_old_space_size=4096

* feat: remove log

* feat: add Wallet configure

* feat: remove hardware wallets
2023-08-13 23:58:57 +08:00
311 changed files with 24830 additions and 5737 deletions

View File

@ -110,7 +110,7 @@ jobs:
with:
start: yarn start
wait-on: 'http://localhost:7001'
wait-on-timeout: 180
wait-on-timeout: 210
working-directory: ./web
- uses: actions/upload-artifact@v3
@ -195,7 +195,7 @@ jobs:
with:
context: .
target: STANDARD
platforms: linux/amd64,linux/arm64
platforms: linux/amd64
push: true
tags: casbin/casdoor:${{steps.get-current-tag.outputs.tag }},casbin/casdoor:latest
@ -205,6 +205,6 @@ jobs:
with:
context: .
target: ALLINONE
platforms: linux/amd64,linux/arm64
platforms: linux/amd64
push: true
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest

View File

@ -1,61 +0,0 @@
name: Migration Test
on:
push:
paths:
- 'object/migrator**'
pull_request:
paths:
- 'object/migrator**'
jobs:
db-migrator-test:
name: db-migrator-test
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_DATABASE: casdoor
MYSQL_ROOT_PASSWORD: 123456
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: '^1.16.5'
- uses: actions/setup-node@v2
with:
node-version: 16
- name: pull casdoor-master-latest
run: |
sudo apt update
sudo apt install git
sudo apt install net-tools
sudo mkdir tmp
cd tmp
sudo git clone https://github.com/casdoor/casdoor.git
cd ..
working-directory: ./
- name: run casdoor-master-latest
run: |
sudo nohup go run main.go &
sudo sleep 2m
working-directory: ./tmp/casdoor
- name: stop casdoor-master-latest
run: |
sudo kill -9 `sudo netstat -anltp | grep 8000 | awk '{print $7}' | cut -d / -f 1`
working-directory: ./
- name: run casdoor-current-version
run: |
sudo nohup go run ./main.go &
sudo sleep 2m
working-directory: ./
- name: test port-8000
run: |
if [[ `sudo netstat -anltp | grep 8000 | awk '{print $7}'` == "" ]];then echo 'db-migrator-test fail' && exit 1;fi;
echo 'db-migrator-test pass'
working-directory: ./

3
.gitignore vendored
View File

@ -30,5 +30,4 @@ commentsRouter*.go
# ignore build result
casdoor
server_linux_arm64
server_linux_amd64
server

View File

@ -1,7 +1,6 @@
FROM node:16.18.0 AS FRONT
WORKDIR /web
COPY ./web .
RUN yarn config set registry https://registry.npmmirror.com
RUN yarn install --frozen-lockfile --network-timeout 1000000 && yarn run build
@ -14,9 +13,6 @@ RUN go test -v -run TestGetVersionInfo ./util/system_test.go ./util/system.go >
FROM alpine:latest AS STANDARD
LABEL MAINTAINER="https://casdoor.org/"
ARG USER=casdoor
ARG TARGETOS
ARG TARGETARCH
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add --update sudo
@ -31,7 +27,7 @@ RUN adduser -D $USER -u 1000 \
USER 1000
WORKDIR /
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server_${BUILDX_ARCH} ./server
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server ./server
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/swagger ./swagger
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/conf/app.conf ./conf/app.conf
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
@ -50,21 +46,17 @@ RUN apt update \
FROM db AS ALLINONE
LABEL MAINTAINER="https://casdoor.org/"
ARG TARGETOS
ARG TARGETARCH
ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
RUN apt update
RUN apt install -y ca-certificates && update-ca-certificates
WORKDIR /
COPY --from=BACK /go/src/casdoor/server_${BUILDX_ARCH} ./server
COPY --from=BACK /go/src/casdoor/server ./server
COPY --from=BACK /go/src/casdoor/swagger ./swagger
COPY --from=BACK /go/src/casdoor/docker-entrypoint.sh /docker-entrypoint.sh
COPY --from=BACK /go/src/casdoor/conf/app.conf ./conf/app.conf
COPY --from=BACK /go/src/casdoor/version_info.txt ./go/src/casdoor/version_info.txt
COPY --from=FRONT /web/build ./web/build
RUN mkdir tempFiles
ENTRYPOINT ["/bin/bash"]
CMD ["/docker-entrypoint.sh"]

View File

@ -11,7 +11,7 @@
<img alt="GitHub Workflow Status (branch)" src="https://github.com/casdoor/casdoor/workflows/Build/badge.svg?style=flat-square">
</a>
<a href="https://github.com/casdoor/casdoor/releases/latest">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casbin/casdoor.svg">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/casdoor/casdoor.svg">
</a>
<a href="https://hub.docker.com/repository/docker/casbin/casdoor">
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
@ -23,16 +23,16 @@
<img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/casdoor/casdoor?style=flat-square">
</a>
<a href="https://github.com/casdoor/casdoor/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/casbin/casdoor?style=flat-square" alt="license">
<img src="https://img.shields.io/github/license/casdoor/casdoor?style=flat-square" alt="license">
</a>
<a href="https://github.com/casdoor/casdoor/issues">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casbin/casdoor?style=flat-square">
<img alt="GitHub issues" src="https://img.shields.io/github/issues/casdoor/casdoor?style=flat-square">
</a>
<a href="#">
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/casdoor?style=flat-square">
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casdoor/casdoor?style=flat-square">
</a>
<a href="https://github.com/casdoor/casdoor/network">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casbin/casdoor?style=flat-square">
<img alt="GitHub forks" src="https://img.shields.io/github/forks/casdoor/casdoor?style=flat-square">
</a>
<a href="https://crowdin.com/project/casdoor-site">
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">

View File

@ -46,6 +46,7 @@ p, *, *, POST, /api/login, *, *
p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/logout, *, *
p, *, *, POST, /api/callback, *, *
p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, *
p, *, *, GET, /api/user, *, *
@ -80,6 +81,7 @@ p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /cas, *, *
p, *, *, *, /scim, *, *
p, *, *, *, /api/webauthn, *, *
p, *, *, GET, /api/get-release, *, *
p, *, *, GET, /api/get-default-application, *, *
@ -87,12 +89,14 @@ p, *, *, GET, /api/get-prometheus-info, *, *
p, *, *, *, /api/metrics, *, *
p, *, *, GET, /api/get-pricing, *, *
p, *, *, GET, /api/get-plan, *, *
p, *, *, GET, /api/get-subscription, *, *
p, *, *, GET, /api/get-provider, *, *
p, *, *, GET, /api/get-organization-names, *, *
`
sa := stringadapter.NewAdapter(ruleText)
// load all rules from string adapter to enforcer's memory
err := sa.LoadPolicy(Enforcer.GetModel())
err = sa.LoadPolicy(Enforcer.GetModel())
if err != nil {
panic(err)
}
@ -119,10 +123,20 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
panic(err)
}
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
if subOwner == "app" {
return true
}
if user != nil {
if user.IsDeleted {
return false
}
if user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
return true
}
}
res, err := Enforcer.Enforce(subOwner, subName, method, urlPath, objOwner, objName)
if err != nil {
panic(err)
@ -133,7 +147,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/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" {
return true
} else if urlPath == "/api/update-user" {
// Allow ordinary users to update their own information

View File

@ -8,5 +8,4 @@ else
echo "Google is blocked, Go proxy is enabled: GOPROXY=https://goproxy.cn,direct"
export GOPROXY="https://goproxy.cn,direct"
fi
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server_linux_amd64 .
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o server_linux_arm64 .
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server .

View File

@ -8,18 +8,23 @@ dbName = casdoor
tableNamePrefix =
showSql = false
redisEndpoint =
defaultStorageProvider =
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
socks5Proxy = "127.0.0.1:10808"
verificationCodeTimeout = 10
initScore = 2000
initScore = 0
logPostOnly = true
origin =
originFrontend =
staticBaseUrl = "https://cdn.casbin.org"
isDemoMode = false
batchSize = 100
enableGzip = true
ldapServerPort = 389
radiusServerPort = 1812
radiusSecret = "secret"
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
initDataFile = "./init_data.json"
initDataFile = "./init_data.json"
frontendBaseDir = "../casdoor"

View File

@ -15,7 +15,7 @@
package conf
import (
"encoding/json"
"fmt"
"os"
"runtime"
"strconv"
@ -24,15 +24,6 @@ import (
"github.com/beego/beego"
)
type Quota struct {
Organization int `json:"organization"`
User int `json:"user"`
Application int `json:"application"`
Provider int `json:"provider"`
}
var quota = &Quota{-1, -1, -1, -1}
func init() {
// this array contains the beego configuration items that may be modified via env
presetConfigItems := []string{"httpport", "appname"}
@ -44,17 +35,6 @@ func init() {
}
}
}
initQuota()
}
func initQuota() {
res := beego.AppConfig.String("quota")
if res != "" {
err := json.Unmarshal([]byte(res), quota)
if err != nil {
panic(err)
}
}
}
func GetConfigString(key string) string {
@ -67,7 +47,7 @@ func GetConfigString(key string) string {
if key == "staticBaseUrl" {
res = "https://cdn.casbin.org"
} else if key == "logConfig" {
res = "{\"filename\": \"logs/casdoor.log\", \"maxdays\":99999, \"perm\":\"0770\"}"
res = fmt.Sprintf("{\"filename\": \"logs/%s.log\", \"maxdays\":99999, \"perm\":\"0770\"}", beego.AppConfig.String("appname"))
}
}
@ -129,10 +109,6 @@ func GetConfigBatchSize() int {
return res
}
func GetConfigQuota() *Quota {
return quota
}
func GetConfigRealDataSourceName(driverName string) string {
var dataSourceName string
if driverName != "mysql" {

48
conf/conf_quota.go Normal file
View File

@ -0,0 +1,48 @@
// 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 conf
import (
"encoding/json"
"github.com/beego/beego"
)
type Quota struct {
Organization int `json:"organization"`
User int `json:"user"`
Application int `json:"application"`
Provider int `json:"provider"`
}
var quota = &Quota{-1, -1, -1, -1}
func init() {
initQuota()
}
func initQuota() {
res := beego.AppConfig.String("quota")
if res != "" {
err := json.Unmarshal([]byte(res), quota)
if err != nil {
panic(err)
}
}
}
func GetConfigQuota() *Quota {
return quota
}

View File

@ -18,7 +18,6 @@ import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/casdoor/casdoor/form"
@ -119,20 +118,10 @@ func (c *ApiController) Signup() {
}
}
id := util.GenerateId()
if application.GetSignupItemRule("ID") == "Incremental" {
lastUser, err := object.GetLastUser(authForm.Organization)
if err != nil {
c.ResponseError(err.Error())
return
}
lastIdInt := -1
if lastUser != nil {
lastIdInt = util.ParseInt(lastUser.Id)
}
id = strconv.Itoa(lastIdInt + 1)
id, err := object.GenerateIdForNewUser(application)
if err != nil {
c.ResponseError(err.Error())
return
}
username := authForm.Username
@ -140,25 +129,28 @@ func (c *ApiController) Signup() {
username = id
}
password := authForm.Password
msg = object.CheckPasswordComplexityByOrg(organization, password)
if msg != "" {
c.ResponseError(msg)
return
}
initScore, err := organization.GetInitScore()
if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
return
}
userType := "normal-user"
if authForm.Plan != "" && authForm.Pricing != "" {
err = object.CheckPricingAndPlan(authForm.Organization, authForm.Pricing, authForm.Plan)
if err != nil {
c.ResponseError(err.Error())
return
}
userType = "paid-user"
}
user := &object.User{
Owner: authForm.Organization,
Name: username,
CreatedTime: util.GetCurrentTime(),
Id: id,
Type: "normal-user",
Type: userType,
Password: authForm.Password,
DisplayName: authForm.Name,
Avatar: organization.DefaultAvatar,
@ -171,7 +163,6 @@ func (c *ApiController) Signup() {
Region: authForm.Region,
Score: initScore,
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
SignupApplication: application.Name,
@ -211,7 +202,7 @@ func (c *ApiController) Signup() {
return
}
if application.HasPromptPage() {
if application.HasPromptPage() && user.Type == "normal-user" {
// The prompt page needs the user to be signed in
c.SetSessionUsername(user.GetId())
}
@ -228,15 +219,6 @@ func (c *ApiController) Signup() {
return
}
isSignupFromPricing := authForm.Plan != "" && authForm.Pricing != ""
if isSignupFromPricing {
_, err = object.Subscribe(organization.Name, user.Name, authForm.Plan, authForm.Pricing)
if err != nil {
c.ResponseError(err.Error())
return
}
}
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
@ -316,27 +298,32 @@ func (c *ApiController) Logout() {
return
}
if application.IsRedirectUriValid(redirectUri) {
if user == "" {
user = util.GetId(token.Organization, token.User)
}
if user == "" {
user = util.GetId(token.Organization, token.User)
}
c.ClearUserSession()
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
owner, username := util.GetOwnerAndNameFromId(user)
c.ClearUserSession()
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
owner, username := util.GetOwnerAndNameFromId(user)
_, err := object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
if err != nil {
c.ResponseError(err.Error())
_, err = object.DeleteSessionId(util.GetSessionId(owner, username, object.CasdoorApplication), c.Ctx.Input.CruSession.SessionID())
if err != nil {
c.ResponseError(err.Error())
return
}
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
if redirectUri == "" {
c.ResponseOk()
return
} else {
if application.IsRedirectUriValid(redirectUri) {
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
} else {
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
return
}
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
c.Ctx.Redirect(http.StatusFound, fmt.Sprintf("%s?state=%s", strings.TrimRight(redirectUri, "/"), state))
} else {
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
return
}
}
}

View File

@ -20,7 +20,6 @@ import (
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
)
// GetAdapters
@ -144,92 +143,3 @@ func (c *ApiController) DeleteAdapter() {
c.Data["json"] = wrapActionResponse(object.DeleteAdapter(&adapter))
c.ServeJSON()
}
func (c *ApiController) GetPolicies() {
id := c.Input().Get("id")
adapter, err := object.GetAdapter(id)
if err != nil {
c.ResponseError(err.Error())
return
}
policies, err := object.GetPolicies(adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(policies)
}
func (c *ApiController) UpdatePolicy() {
id := c.Input().Get("id")
adapter, err := object.GetAdapter(id)
if err != nil {
c.ResponseError(err.Error())
return
}
var policies []xormadapter.CasbinRule
err = json.Unmarshal(c.Ctx.Input.RequestBody, &policies)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UpdatePolicy(util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) AddPolicy() {
id := c.Input().Get("id")
adapter, err := object.GetAdapter(id)
if err != nil {
c.ResponseError(err.Error())
return
}
var policy xormadapter.CasbinRule
err = json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.AddPolicy(util.CasbinToSlice(policy), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) RemovePolicy() {
id := c.Input().Get("id")
adapter, err := object.GetAdapter(id)
if err != nil {
c.ResponseError(err.Error())
return
}
var policy xormadapter.CasbinRule
err = json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.RemovePolicy(util.CasbinToSlice(policy), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}

View File

@ -62,13 +62,13 @@ func (c *ApiController) GetApplications() {
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
app, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
application, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
applications := object.GetMaskedApplications(app, userId)
applications := object.GetMaskedApplications(application, userId)
c.ResponseOk(applications, paginator.Nums())
}
}
@ -84,13 +84,33 @@ func (c *ApiController) GetApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
app, err := object.GetApplication(id)
application, err := object.GetApplication(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedApplication(app, userId))
if c.Input().Get("withKey") != "" && application != nil && application.Cert != "" {
cert, err := object.GetCert(util.GetId(application.Owner, application.Cert))
if err != nil {
c.ResponseError(err.Error())
return
}
if cert == nil {
cert, err = object.GetCert(util.GetId(application.Organization, application.Cert))
if err != nil {
c.ResponseError(err.Error())
return
}
}
if cert != nil {
application.CertPublicKey = cert.Certificate
}
}
c.ResponseOk(object.GetMaskedApplication(application, userId))
}
// GetUserApplication
@ -153,6 +173,12 @@ func (c *ApiController) GetOrganizationApplications() {
return
}
applications, err = object.GetAllowedApplications(applications, userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedApplications(applications, userId))
} else {
limit := util.ParseInt(limit)
@ -164,13 +190,13 @@ func (c *ApiController) GetOrganizationApplications() {
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
app, err := object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
application, err := object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
applications := object.GetMaskedApplications(app, userId)
applications := object.GetMaskedApplications(application, userId)
c.ResponseOk(applications, paginator.Nums())
}
}

View File

@ -20,6 +20,7 @@ import (
"encoding/xml"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
@ -59,7 +60,7 @@ func tokenToResponse(token *object.Token) *Response {
func (c *ApiController) HandleLoggedIn(application *object.Application, user *object.User, form *form.AuthForm) (resp *Response) {
userId := user.GetId()
allowed, err := object.CheckAccessPermission(userId, application)
allowed, err := object.CheckLoginPermission(userId, application)
if err != nil {
c.ResponseError(err.Error(), nil)
return
@ -70,7 +71,7 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
}
// check user's tag
if !user.IsGlobalAdmin && !user.IsAdmin && len(application.Tags) > 0 {
if !user.IsGlobalAdmin() && !user.IsAdmin && len(application.Tags) > 0 {
// only users with the tag that is listed in the application tags can login
if !util.InSlice(application.Tags, user.Tag) {
c.ResponseError(fmt.Sprintf(c.T("auth:User's tag: %s is not listed in the application's tags"), user.Tag))
@ -78,6 +79,46 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
}
}
// check whether paid-user have active subscription
if user.Type == "paid-user" {
subscriptions, err := object.GetSubscriptionsByUser(user.Owner, user.Name)
if err != nil {
c.ResponseError(err.Error())
return
}
existActiveSubscription := false
for _, subscription := range subscriptions {
if subscription.State == object.SubStateActive {
existActiveSubscription = true
break
}
}
if !existActiveSubscription {
// check pending subscription
for _, sub := range subscriptions {
if sub.State == object.SubStatePending {
c.ResponseOk("BuyPlanResult", sub)
return
}
}
// paid-user does not have active or pending subscription, find the default pricing of application
pricing, err := object.GetApplicationDefaultPricing(application.Organization, application.Name)
if err != nil {
c.ResponseError(err.Error())
return
}
if pricing == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"), user.Name, application.Name))
return
} else {
// let the paid-user select plan
c.ResponseOk("SelectPlan", pricing)
return
}
}
}
if form.Type == ResponseTypeLogin {
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
@ -187,11 +228,34 @@ func (c *ApiController) GetApplicationLogin() {
redirectUri := c.Input().Get("redirectUri")
scope := c.Input().Get("scope")
state := c.Input().Get("state")
id := c.Input().Get("id")
loginType := c.Input().Get("type")
msg, application, err := object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
var application *object.Application
var msg string
var err error
if loginType == "code" {
msg, application, err = object.CheckOAuthLogin(clientId, responseType, redirectUri, scope, state, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
} else if loginType == "cas" {
application, err = object.GetApplication(id)
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), id))
return
}
err = object.CheckCasLogin(application, c.GetAcceptLanguage(), redirectUri)
if err != nil {
c.ResponseError(err.Error())
return
}
}
application = object.GetMaskedApplication(application, "")
@ -413,11 +477,10 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:The provider: %s is not enabled for the application"), provider.Name))
return
}
userInfo := &idp.UserInfo{}
if provider.Category == "SAML" {
// SAML
userInfo.Id, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
userInfo, err = object.ParseSamlResponse(authForm.SamlResponse, provider, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
@ -425,7 +488,11 @@ func (c *ApiController) Login() {
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
// OAuth
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
idProvider := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
idProvider, err := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
if err != nil {
c.ResponseError(err.Error())
return
}
if idProvider == nil {
c.ResponseError(fmt.Sprintf(c.T("storage:The provider type: %s is not supported"), provider.Type))
return
@ -460,7 +527,8 @@ func (c *ApiController) Login() {
if authForm.Method == "signup" {
user := &object.User{}
if provider.Category == "SAML" {
user, err = object.GetUser(util.GetId(application.Organization, userInfo.Id))
// The userInfo.Id is the NameID in SAML response, it could be name / email / phone
user, err = object.GetUserByFields(application.Organization, userInfo.Id)
if err != nil {
c.ResponseError(err.Error())
return
@ -551,11 +619,16 @@ func (c *ApiController) Login() {
return
}
userId := userInfo.Id
if userId == "" {
userId = util.GenerateId()
}
user = &object.User{
Owner: application.Organization,
Name: userInfo.Username,
CreatedTime: util.GetCurrentTime(),
Id: util.GenerateId(),
Id: userId,
Type: "normal-user",
DisplayName: userInfo.DisplayName,
Avatar: userInfo.AvatarUrl,
@ -566,7 +639,6 @@ func (c *ApiController) Login() {
Region: userInfo.CountryCode,
Score: initScore,
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
SignupApplication: application.Name,
@ -583,6 +655,15 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to create user, user information is invalid: %s"), util.StructToJson(user)))
return
}
if providerItem.SignupGroup != "" {
user.Groups = []string{providerItem.SignupGroup}
_, err = object.UpdateUser(user.GetId(), user, []string{"groups"}, false)
if err != nil {
c.ResponseError(err.Error())
return
}
}
}
// sync info from 3rd-party if possible
@ -611,6 +692,7 @@ func (c *ApiController) Login() {
record2.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record2) })
} else if provider.Category == "SAML" {
// TODO: since we get the user info from SAML response, we can try to create the user
resp = &Response{Status: "error", Msg: fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(application.Organization, userInfo.Id))}
}
// resp = &Response{Status: "ok", Msg: "", Data: res}
@ -834,3 +916,16 @@ func (c *ApiController) GetCaptchaStatus() {
}
c.ResponseOk(captchaEnabled)
}
// Callback
// @Title Callback
// @Tag Callback API
// @Description Get Login Error Counts
// @router /api/Callback [post]
func (c *ApiController) Callback() {
code := c.GetString("code")
state := c.GetString("state")
frontendCallbackUrl := fmt.Sprintf("/callback?code=%s&state=%s", code, state)
c.Ctx.Redirect(http.StatusFound, frontendCallbackUrl)
}

View File

@ -61,6 +61,10 @@ func (c *ApiController) IsAdminOrSelf(user2 *object.User) bool {
return true
}
if user == nil || user2 == nil {
return false
}
if user.Owner == user2.Owner && user.Name == user2.Name {
return true
}
@ -79,7 +83,7 @@ func (c *ApiController) isGlobalAdmin() (bool, *object.User) {
return false, nil
}
return user.Owner == "built-in" || user.IsGlobalAdmin, user
return user.IsGlobalAdmin(), user
}
func (c *ApiController) getCurrentUser() *object.User {

View File

@ -35,6 +35,11 @@ const (
UnauthorizedService string = "UNAUTHORIZED_SERVICE"
)
func queryUnescape(service string) string {
s, _ := url.QueryUnescape(service)
return s
}
func (c *RootController) CasValidate() {
ticket := c.Input().Get("ticket")
service := c.Input().Get("service")
@ -60,24 +65,25 @@ func (c *RootController) CasServiceValidate() {
if !strings.HasPrefix(ticket, "ST") {
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
}
c.CasP3ServiceAndProxyValidate()
c.CasP3ProxyValidate()
}
func (c *RootController) CasProxyValidate() {
// https://apereo.github.io/cas/6.6.x/protocol/CAS-Protocol-Specification.html#26-proxyvalidate-cas-20
// "/proxyValidate" should accept both service tickets and proxy tickets.
c.CasP3ProxyValidate()
}
func (c *RootController) CasP3ServiceValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
if !strings.HasPrefix(ticket, "PT") {
if !strings.HasPrefix(ticket, "ST") {
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
}
c.CasP3ServiceAndProxyValidate()
c.CasP3ProxyValidate()
}
func queryUnescape(service string) string {
s, _ := url.QueryUnescape(service)
return s
}
func (c *RootController) CasP3ServiceAndProxyValidate() {
func (c *RootController) CasP3ProxyValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
service := c.Input().Get("service")
@ -115,15 +121,17 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
pgtiou := serviceResponse.Success.ProxyGrantingTicket
// todo: check whether it is https
pgtUrlObj, err := url.Parse(pgtUrl)
if err != nil {
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, err.Error(), format)
return
}
if pgtUrlObj.Scheme != "https" {
c.sendCasAuthenticationResponseErr(InvalidProxyCallback, "callback is not https", format)
return
}
// make a request to pgturl passing pgt and pgtiou
if err != nil {
c.sendCasAuthenticationResponseErr(InternalError, err.Error(), format)
return
}
param := pgtUrlObj.Query()
param.Add("pgtId", pgt)
param.Add("pgtIou", pgtiou)
@ -263,7 +271,6 @@ func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format stri
Message: msg,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()

View File

@ -37,6 +37,11 @@ func (c *ApiController) Enforce() {
resourceId := c.Input().Get("resourceId")
enforcerId := c.Input().Get("enforcerId")
if len(c.Ctx.Input.RequestBody) == 0 {
c.ResponseError("The request body should not be empty")
return
}
var request object.CasbinRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
if err != nil {

View File

@ -20,6 +20,7 @@ import (
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
)
// GetEnforcers
@ -74,6 +75,7 @@ func (c *ApiController) GetEnforcers() {
// @router /get-enforcer [get]
func (c *ApiController) GetEnforcer() {
id := c.Input().Get("id")
loadModelCfg := c.Input().Get("loadModelCfg")
enforcer, err := object.GetEnforcer(id)
if err != nil {
@ -81,6 +83,13 @@ func (c *ApiController) GetEnforcer() {
return
}
if loadModelCfg == "true" && enforcer.Model != "" {
err := enforcer.LoadModelCfg()
if err != nil {
return
}
}
c.ResponseOk(enforcer)
}
@ -143,3 +152,88 @@ func (c *ApiController) DeleteEnforcer() {
c.Data["json"] = wrapActionResponse(object.DeleteEnforcer(&enforcer))
c.ServeJSON()
}
func (c *ApiController) GetPolicies() {
id := c.Input().Get("id")
adapterId := c.Input().Get("adapterId")
if adapterId != "" {
adapter, err := object.GetAdapter(adapterId)
if err != nil {
c.ResponseError(err.Error())
return
}
err = adapter.InitAdapter()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
return
}
policies, err := object.GetPolicies(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(policies)
}
func (c *ApiController) UpdatePolicy() {
id := c.Input().Get("id")
var policies []xormadapter.CasbinRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policies)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UpdatePolicy(id, policies[0].Ptype, util.CasbinToSlice(policies[0]), util.CasbinToSlice(policies[1]))
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) AddPolicy() {
id := c.Input().Get("id")
var policy xormadapter.CasbinRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.AddPolicy(id, policy.Ptype, util.CasbinToSlice(policy))
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
func (c *ApiController) RemovePolicy() {
id := c.Input().Get("id")
var policy xormadapter.CasbinRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &policy)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.RemovePolicy(id, policy.Ptype, util.CasbinToSlice(policy))
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}

View File

@ -0,0 +1,35 @@
// 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 "github.com/casdoor/casdoor/object"
// GetDashboard
// @Title GetDashboard
// @Tag GetDashboard API
// @Description get information of dashboard
// @Success 200 {object} controllers.Response The Response object
// @router /get-dashboard [get]
func (c *ApiController) GetDashboard() {
owner := c.Input().Get("owner")
data, err := object.GetDashboard(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(data)
}

View File

@ -45,13 +45,13 @@ func (c *ApiController) Unlink() {
// the user will be unlinked from the provider
unlinkedUser := form.User
if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin {
if user.Id != unlinkedUser.Id && !user.IsGlobalAdmin() {
// if the user is not the same as the one we are unlinking, we need to make sure the user is the global admin.
c.ResponseError(c.T("link:You are not the global admin, you can't unlink other users"))
return
}
if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin {
if user.Id == unlinkedUser.Id && !user.IsGlobalAdmin() {
// if the user is unlinking themselves, should check the provider can be unlinked, if not, we should return an error.
application, err := object.GetApplicationByUser(user)
if err != nil {

View File

@ -183,8 +183,6 @@ func (c *ApiController) DeleteOrganization() {
func (c *ApiController) GetDefaultApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
redirectUri := c.Input().Get("redirectUri")
typ := c.Input().Get("type")
application, err := object.GetDefaultApplication(id)
if err != nil {
@ -192,14 +190,6 @@ func (c *ApiController) GetDefaultApplication() {
return
}
if typ == "cas" {
err = object.CheckCasRestrict(application, c.GetAcceptLanguage(), redirectUri)
if err != nil {
c.ResponseError(err.Error())
return
}
}
maskedApplication := object.GetMaskedApplication(application, userId)
c.ResponseOk(maskedApplication)
}

View File

@ -176,11 +176,10 @@ func (c *ApiController) DeletePayment() {
func (c *ApiController) NotifyPayment() {
owner := c.Ctx.Input.Param(":owner")
paymentName := c.Ctx.Input.Param(":payment")
orderId := c.Ctx.Input.Param("order")
body := c.Ctx.Input.RequestBody
payment, err := object.NotifyPayment(c.Ctx.Request, body, owner, paymentName, orderId)
payment, err := object.NotifyPayment(body, owner, paymentName)
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -16,6 +16,7 @@ package controllers
import (
"fmt"
"os"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
@ -32,16 +33,15 @@ func (c *ApiController) UploadPermissions() {
}
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
path := util.GetUploadXlsxPath(fileId)
util.EnsureFileFolderExists(path)
defer os.Remove(path)
err = saveFile(path, &file)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UploadPermissions(owner, fileId)
affected, err := object.UploadPermissions(owner, path)
if err != nil {
c.ResponseError(err.Error())
}

View File

@ -83,7 +83,7 @@ func (c *ApiController) GetPlan() {
return
}
if includeOption {
if plan != nil && includeOption {
options, err := object.GetPermissionsByRole(plan.Role)
if err != nil {
c.ResponseError(err.Error())
@ -93,11 +93,9 @@ func (c *ApiController) GetPlan() {
for _, option := range options {
plan.Options = append(plan.Options, option.DisplayName)
}
c.ResponseOk(plan)
} else {
c.ResponseOk(plan)
}
c.ResponseOk(plan)
}
// UpdatePlan
@ -110,14 +108,29 @@ func (c *ApiController) GetPlan() {
// @router /update-plan [post]
func (c *ApiController) UpdatePlan() {
id := c.Input().Get("id")
owner := util.GetOwnerFromId(id)
var plan object.Plan
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
if err != nil {
c.ResponseError(err.Error())
return
}
if plan.Product != "" {
productId := util.GetId(owner, plan.Product)
product, err := object.GetProduct(productId)
if err != nil {
c.ResponseError(err.Error())
return
}
if product != nil {
object.UpdateProductForPlan(&plan, product)
_, err = object.UpdateProduct(productId, product)
if err != nil {
c.ResponseError(err.Error())
return
}
}
}
c.Data["json"] = wrapActionResponse(object.UpdatePlan(id, &plan))
c.ServeJSON()
}
@ -136,7 +149,14 @@ func (c *ApiController) AddPlan() {
c.ResponseError(err.Error())
return
}
// Create a related product for plan
product := object.CreateProductForPlan(&plan)
_, err = object.AddProduct(product)
if err != nil {
c.ResponseError(err.Error())
return
}
plan.Product = product.Name
c.Data["json"] = wrapActionResponse(object.AddPlan(&plan))
c.ServeJSON()
}
@ -155,7 +175,13 @@ func (c *ApiController) DeletePlan() {
c.ResponseError(err.Error())
return
}
if plan.Product != "" {
_, err = object.DeleteProduct(&object.Product{Owner: plan.Owner, Name: plan.Product})
if err != nil {
c.ResponseError(err.Error())
return
}
}
c.Data["json"] = wrapActionResponse(object.DeletePlan(&plan))
c.ServeJSON()
}

View File

@ -161,10 +161,17 @@ func (c *ApiController) DeleteProduct() {
// @router /buy-product [post]
func (c *ApiController) BuyProduct() {
id := c.Input().Get("id")
providerName := c.Input().Get("providerName")
host := c.Ctx.Request.Host
userId := c.GetSessionUsername()
providerName := c.Input().Get("providerName")
// buy `pricingName/planName` for `paidUserName`
pricingName := c.Input().Get("pricingName")
planName := c.Input().Get("planName")
paidUserName := c.Input().Get("userName")
owner, _ := util.GetOwnerAndNameFromId(id)
userId := util.GetId(owner, paidUserName)
if paidUserName == "" {
userId = c.GetSessionUsername()
}
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
@ -175,17 +182,16 @@ func (c *ApiController) BuyProduct() {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return
}
payUrl, orderId, err := object.BuyProduct(id, providerName, user, host)
payment, err := object.BuyProduct(id, user, providerName, pricingName, planName, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payUrl, orderId)
c.ResponseOk(payment)
}

View File

@ -1,121 +0,0 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetRecords
// @Title GetRecords
// @Tag Record API
// @Description get all records
// @Param pageSize query string true "The size of each page"
// @Param p query string true "The number of the page"
// @Success 200 {object} object.Record The Response object
// @router /get-records [get]
func (c *ApiController) GetRecords() {
organization, ok := c.RequireAdmin()
if !ok {
return
}
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")
organizationName := c.Input().Get("organizationName")
if limit == "" || page == "" {
records, err := object.GetRecords()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(records)
} else {
limit := util.ParseInt(limit)
if c.IsGlobalAdmin() && organizationName != "" {
organization = organizationName
}
filterRecord := &object.Record{Organization: organization}
count, err := object.GetRecordCount(field, value, filterRecord)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
records, err := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder, filterRecord)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(records, paginator.Nums())
}
}
// GetRecordsByFilter
// @Tag Record API
// @Title GetRecordsByFilter
// @Description get records by filter
// @Param filter body string true "filter Record message"
// @Success 200 {object} object.Record The Response object
// @router /get-records-filter [post]
func (c *ApiController) GetRecordsByFilter() {
body := string(c.Ctx.Input.RequestBody)
record := &object.Record{}
err := util.JsonToStruct(body, record)
if err != nil {
c.ResponseError(err.Error())
return
}
records, err := object.GetRecordsByField(record)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(records)
}
// AddRecord
// @Title AddRecord
// @Tag Record API
// @Description add a record
// @Param body body object.Record true "The details of the record"
// @Success 200 {object} controllers.Response The Response object
// @router /add-record [post]
func (c *ApiController) AddRecord() {
var record object.Record
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddRecord(&record))
c.ServeJSON()
}

View File

@ -272,6 +272,11 @@ func (c *ApiController) UploadResource() {
return
}
if username == "Built-in-Untracked" {
c.ResponseOk(fileUrl, objectKey)
return
}
if createdTime == "" {
createdTime = util.GetCurrentTime()
}
@ -361,6 +366,9 @@ func (c *ApiController) UploadResource() {
return
}
if user.Properties == nil {
user.Properties = map[string]string{}
}
user.Properties[tag] = fileUrl
user.Properties["isIdCardVerified"] = "false"
_, err = object.UpdateUser(user.GetId(), user, []string{"properties"}, false)

View File

@ -16,6 +16,7 @@ package controllers
import (
"fmt"
"os"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
@ -32,16 +33,15 @@ func (c *ApiController) UploadRoles() {
}
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
path := util.GetUploadXlsxPath(fileId)
util.EnsureFileFolderExists(path)
defer os.Remove(path)
err = saveFile(path, &file)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UploadRoles(owner, fileId)
affected, err := object.UploadRoles(owner, path)
if err != nil {
c.ResponseError(err.Error())
}

View File

@ -33,7 +33,13 @@ func (c *ApiController) GetSamlMeta() {
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
return
}
metadata, _ := object.GetSamlMeta(application, host)
metadata, err := object.GetSamlMeta(application, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["xml"] = metadata
c.ServeXML()
}

27
controllers/scim.go Normal file
View File

@ -0,0 +1,27 @@
// 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 (
"strings"
"github.com/casdoor/casdoor/scim"
)
func (c *RootController) HandleScim() {
path := c.Ctx.Request.URL.Path
c.Ctx.Request.URL.Path = strings.TrimPrefix(path, "/scim")
scim.Server.ServeHTTP(c.Ctx.ResponseWriter, c.Ctx.Request)
}

View File

@ -40,6 +40,10 @@ type SmsForm struct {
OrgId string `json:"organizationId"` // e.g. "admin/built-in"
}
type NotificationForm struct {
Content string `json:"content"`
}
// SendEmail
// @Title SendEmail
// @Tag Service API
@ -156,3 +160,33 @@ func (c *ApiController) SendSms() {
c.ResponseOk()
}
// SendNotification
// @Title SendNotification
// @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
// @router /api/send-notification [post]
func (c *ApiController) SendNotification() {
provider, err := c.GetProviderFromContext("Notification")
if err != nil {
c.ResponseError(err.Error())
return
}
var notificationForm NotificationForm
err = json.Unmarshal(c.Ctx.Input.RequestBody, &notificationForm)
if err != nil {
c.ResponseError(err.Error())
return
}
err = object.SendNotification(provider, notificationForm.Content)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}

View File

@ -160,7 +160,11 @@ func (c *ApiController) RunSyncer() {
return
}
object.RunSyncer(syncer)
err = object.RunSyncer(syncer)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}

View File

@ -47,16 +47,16 @@ func (c *ApiController) GetSystemInfo() {
// @router /get-version-info [get]
func (c *ApiController) GetVersionInfo() {
versionInfo, err := util.GetVersionInfo()
if versionInfo.Version == "" {
versionInfo, err = util.GetVersionInfoFromFile()
if err != nil {
c.ResponseError(err.Error())
return
}
if versionInfo.Version != "" {
c.ResponseOk(versionInfo)
return
}
versionInfo, err = util.GetVersionInfoFromFile()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(versionInfo)
}

View File

@ -156,7 +156,7 @@ func (c *ApiController) DeleteToken() {
// @Success 200 {object} object.TokenWrapper The Response object
// @Success 400 {object} object.TokenError The Response object
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/access_token [post]
// @router api/login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type")
refreshToken := c.Input().Get("refresh_token")

View File

@ -160,35 +160,51 @@ func (c *ApiController) GetUser() {
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
}
if owner == "" {
owner = util.GetOwnerFromId(id)
}
var user *object.User
if id == "" && owner == "" {
switch {
case email != "":
user, err = object.GetUserByEmailOnly(email)
case phone != "":
user, err = object.GetUserByPhoneOnly(phone)
case userId != "":
user, err = object.GetUserByUserIdOnly(userId)
}
} else {
if owner == "" {
owner = util.GetOwnerFromId(id)
}
organization, err := object.GetOrganization(util.GetId("admin", owner))
if err != nil {
c.ResponseError(err.Error())
return
}
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
if !hasPermission {
var organization *object.Organization
organization, err = object.GetOrganization(util.GetId("admin", owner))
if err != nil {
c.ResponseError(err.Error())
return
}
}
if organization == nil {
c.ResponseError(fmt.Sprintf("the organization: %s is not found", owner))
return
}
var user *object.User
switch {
case email != "":
user, err = object.GetUserByEmail(owner, email)
case phone != "":
user, err = object.GetUserByPhone(owner, phone)
case userId != "":
user = userFromUserId
default:
user, err = object.GetUser(id)
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, false, c.GetAcceptLanguage())
if !hasPermission {
c.ResponseError(err.Error())
return
}
}
switch {
case email != "":
user, err = object.GetUserByEmail(owner, email)
case phone != "":
user, err = object.GetUserByPhone(owner, phone)
case userId != "":
user = userFromUserId
default:
user, err = object.GetUser(id)
}
}
if err != nil {
@ -258,6 +274,13 @@ func (c *ApiController) UpdateUser() {
return
}
if c.Input().Get("allowEmpty") == "" {
if user.DisplayName == "" {
c.ResponseError(c.T("user:Display name cannot be empty"))
return
}
}
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
c.ResponseError(msg)
return
@ -441,12 +464,25 @@ func (c *ApiController) SetPassword() {
}
targetUser, err := object.GetUser(userId)
if targetUser == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return
}
if err != nil {
c.ResponseError(err.Error())
return
}
if oldPassword != "" {
isAdmin := c.IsAdmin()
if isAdmin {
if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
return
}
}
} else if code == "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
@ -540,11 +576,11 @@ func (c *ApiController) GetUserCount() {
c.ResponseOk(count)
}
// AddUserkeys
// @Title AddUserkeys
// AddUserKeys
// @Title AddUserKeys
// @router /add-user-keys [post]
// @Tag User API
func (c *ApiController) AddUserkeys() {
func (c *ApiController) AddUserKeys() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
@ -553,7 +589,7 @@ func (c *ApiController) AddUserkeys() {
}
isAdmin := c.IsAdmin()
affected, err := object.AddUserkeys(&user, isAdmin)
affected, err := object.AddUserKeys(&user, isAdmin)
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -48,17 +48,17 @@ func (c *ApiController) UploadUsers() {
c.ResponseError(err.Error())
return
}
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
path := util.GetUploadXlsxPath(fileId)
util.EnsureFileFolderExists(path)
defer os.Remove(path)
err = saveFile(path, &file)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UploadUsers(owner, fileId)
affected, err := object.UploadUsers(owner, path)
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -96,6 +96,13 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
return nil, false
}
if strings.HasPrefix(userId, "app/") {
tmpUserId := c.Input().Get("userId")
if tmpUserId != "" {
userId = tmpUserId
}
}
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())

View File

@ -142,6 +142,10 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError(err.Error())
return
}
if provider == nil {
c.ResponseError(fmt.Sprintf("please add an Email provider to the \"Providers\" list for the application: %s", application.Name))
return
}
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, vform.Dest)
case object.VerifyTypePhone:
@ -184,6 +188,10 @@ func (c *ApiController) SendVerificationCode() {
c.ResponseError(err.Error())
return
}
if provider == nil {
c.ResponseError(fmt.Sprintf("please add a SMS provider to the \"Providers\" list for the application: %s", application.Name))
return
}
if phone, ok := util.GetE164Number(vform.Dest, vform.CountryCode); !ok {
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), vform.CountryCode))

View File

@ -154,6 +154,7 @@ func (c *ApiController) WebAuthnSigninBegin() {
// @router /webauthn/signin/finish [post]
func (c *ApiController) WebAuthnSigninFinish() {
responseType := c.Input().Get("responseType")
clientId := c.Input().Get("clientId")
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
@ -182,7 +183,13 @@ func (c *ApiController) WebAuthnSigninFinish() {
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
application, err := object.GetApplicationByUser(user)
var application *object.Application
if clientId != "" && (responseType == ResponseTypeCode) {
application, err = object.GetApplicationByClientId(clientId)
} else {
application, err = object.GetApplicationByUser(user)
}
if err != nil {
c.ResponseError(err.Error())
return

229
email/azure_acs.go Normal file
View File

@ -0,0 +1,229 @@
// 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 email
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/google/uuid"
)
const (
importanceNormal = "normal"
sendEmailEndpoint = "/emails:send"
apiVersion = "2023-03-31"
)
type Email struct {
Recipients Recipients `json:"recipients"`
SenderAddress string `json:"senderAddress"`
Content Content `json:"content"`
Headers []CustomHeader `json:"headers"`
Tracking bool `json:"disableUserEngagementTracking"`
Importance string `json:"importance"`
ReplyTo []EmailAddress `json:"replyTo"`
Attachments []Attachment `json:"attachments"`
}
type Recipients struct {
To []EmailAddress `json:"to"`
CC []EmailAddress `json:"cc"`
BCC []EmailAddress `json:"bcc"`
}
type EmailAddress struct {
DisplayName string `json:"displayName"`
Address string `json:"address"`
}
type Content struct {
Subject string `json:"subject"`
HTML string `json:"html"`
PlainText string `json:"plainText"`
}
type CustomHeader struct {
Name string `json:"name"`
Value string `json:"value"`
}
type Attachment struct {
Content string `json:"contentBytesBase64"`
AttachmentType string `json:"attachmentType"`
Name string `json:"name"`
}
type ErrorResponse struct {
Error CommunicationError `json:"error"`
}
// CommunicationError contains the error code and message
type CommunicationError struct {
Code string `json:"code"`
Message string `json:"message"`
}
type AzureACSEmailProvider struct {
AccessKey string
Endpoint string
}
func NewAzureACSEmailProvider(accessKey string, endpoint string) *AzureACSEmailProvider {
return &AzureACSEmailProvider{
AccessKey: accessKey,
Endpoint: endpoint,
}
}
func newEmail(fromAddress string, toAddress string, subject string, content string) *Email {
return &Email{
Recipients: Recipients{
To: []EmailAddress{
{
DisplayName: toAddress,
Address: toAddress,
},
},
},
SenderAddress: fromAddress,
Content: Content{
Subject: subject,
HTML: content,
},
Importance: importanceNormal,
}
}
func (a *AzureACSEmailProvider) sendEmail(e *Email) error {
postBody, err := json.Marshal(e)
if err != nil {
return fmt.Errorf("email JSON marshall failed: %s", err)
}
bodyBuffer := bytes.NewBuffer(postBody)
endpoint := strings.TrimSuffix(a.Endpoint, "/")
url := fmt.Sprintf("%s/emails:send?api-version=2023-03-31", endpoint)
req, err := http.NewRequest("POST", url, bodyBuffer)
if err != nil {
return fmt.Errorf("error creating AzureACS API request: %s", err)
}
// Sign the request using the AzureACS access key and HMAC-SHA256
err = signRequestHMAC(a.AccessKey, req)
if err != nil {
return fmt.Errorf("error signing AzureACS API request: %s", err)
}
req.Header.Set("Content-Type", "application/json")
// Some important header
req.Header.Set("repeatability-request-id", uuid.New().String())
req.Header.Set("repeatability-first-sent", time.Now().UTC().Format(http.TimeFormat))
// Send request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("error sending AzureACS API request: %s", err)
}
defer resp.Body.Close()
// Response error Handling
if resp.StatusCode == http.StatusBadRequest || resp.StatusCode == http.StatusUnauthorized {
commError := ErrorResponse{}
err = json.NewDecoder(resp.Body).Decode(&commError)
if err != nil {
return err
}
return fmt.Errorf("error sending email: %s", commError.Error.Message)
}
if resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("error sending email: status: %d", resp.StatusCode)
}
return nil
}
func signRequestHMAC(secret string, req *http.Request) error {
method := req.Method
host := req.URL.Host
pathAndQuery := req.URL.Path
if req.URL.RawQuery != "" {
pathAndQuery = pathAndQuery + "?" + req.URL.RawQuery
}
var content []byte
var err error
if req.Body != nil {
content, err = io.ReadAll(req.Body)
if err != nil {
// return err
content = []byte{}
}
}
req.Body = io.NopCloser(bytes.NewBuffer(content))
key, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return fmt.Errorf("error decoding secret: %s", err)
}
timestamp := time.Now().UTC().Format(http.TimeFormat)
contentHash := GetContentHashBase64(content)
stringToSign := fmt.Sprintf("%s\n%s\n%s;%s;%s", strings.ToUpper(method), pathAndQuery, timestamp, host, contentHash)
signature := GetHmac(stringToSign, key)
req.Header.Set("x-ms-content-sha256", contentHash)
req.Header.Set("x-ms-date", timestamp)
req.Header.Set("Authorization", "HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature="+signature)
return nil
}
func GetContentHashBase64(content []byte) string {
hasher := sha256.New()
hasher.Write(content)
return base64.StdEncoding.EncodeToString(hasher.Sum(nil))
}
func GetHmac(content string, key []byte) string {
hmac := hmac.New(sha256.New, key)
hmac.Write([]byte(content))
return base64.StdEncoding.EncodeToString(hmac.Sum(nil))
}
func (a *AzureACSEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
e := newEmail(fromAddress, toAddress, subject, content)
return a.sendEmail(e)
}

27
email/provider.go Normal file
View File

@ -0,0 +1,27 @@
// 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 email
type EmailProvider interface {
Send(fromAddress string, fromName, toAddress string, subject string, content string) error
}
func GetEmailProvider(typ string, clientId string, clientSecret string, host string, port int, disableSsl bool) EmailProvider {
if typ == "Azure ACS" {
return NewAzureACSEmailProvider(clientSecret, host)
} else {
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
}
}

49
email/smtp.go Normal file
View File

@ -0,0 +1,49 @@
// 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 email
import (
"crypto/tls"
"github.com/casdoor/gomail/v2"
)
type SmtpEmailProvider struct {
Dialer *gomail.Dialer
}
func NewSmtpEmailProvider(userName string, password string, host string, port int, typ string, disableSsl bool) *SmtpEmailProvider {
dialer := &gomail.Dialer{}
dialer = gomail.NewDialer(host, port, userName, password)
if typ == "SUBMAIL" {
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
dialer.SSL = !disableSsl
return &SmtpEmailProvider{Dialer: dialer}
}
func (s *SmtpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
message := gomail.NewMessage()
message.SetAddressHeader("From", fromAddress, fromName)
message.SetHeader("To", toAddress)
message.SetHeader("Subject", subject)
message.SetBody("text/html", content)
message.SkipUsernameCheck = true
return s.Dialer.DialAndSend(message)
}

View File

@ -17,17 +17,18 @@ package form
type AuthForm struct {
Type string `json:"type"`
Organization string `json:"organization"`
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Email string `json:"email"`
Phone string `json:"phone"`
Affiliation string `json:"affiliation"`
IdCard string `json:"idCard"`
Region string `json:"region"`
Organization string `json:"organization"`
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Email string `json:"email"`
Phone string `json:"phone"`
Affiliation string `json:"affiliation"`
IdCard string `json:"idCard"`
Region string `json:"region"`
InvitationCode string `json:"invitationCode"`
Application string `json:"application"`
ClientId string `json:"clientId"`

34
go.mod
View File

@ -6,19 +6,21 @@ require (
github.com/Masterminds/squirrel v1.5.3
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
github.com/aliyun/alibaba-cloud-sdk-go v1.62.188 // indirect
github.com/aws/aws-sdk-go v1.44.4
github.com/aws/aws-sdk-go v1.45.5
github.com/beego/beego v1.12.12
github.com/beevik/etree v1.1.0
github.com/casbin/casbin v1.9.1 // indirect
github.com/casbin/casbin/v2 v2.30.1
github.com/casdoor/go-sms-sender v0.12.0
github.com/casbin/casbin/v2 v2.77.2
github.com/casdoor/go-sms-sender v0.15.0
github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/notify v0.45.0
github.com/casdoor/oss v1.3.0
github.com/casdoor/xorm-adapter/v3 v3.0.4
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
github.com/denisenkom/go-mssqldb v0.9.0
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
github.com/fogleman/gg v1.3.0
github.com/forestmgy/ldapserver v1.1.0
github.com/go-git/go-git/v5 v5.6.0
@ -26,13 +28,13 @@ require (
github.com/go-mysql-org/go-mysql v1.7.0
github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.6.0
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/go-webauthn/webauthn v0.6.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.3.0
github.com/gorilla/mux v1.7.3 // indirect
github.com/google/uuid v1.3.1
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v1.2.21
github.com/lib/pq v1.10.2
github.com/lib/pq v1.10.9
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/markbates/goth v1.75.2
github.com/mitchellh/mapstructure v1.5.0
@ -45,24 +47,28 @@ require (
github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.0
github.com/russellhaering/goxmldsig v1.2.0
github.com/satori/go.uuid v1.2.0
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.3
github.com/stretchr/testify v1.8.4
github.com/stripe/stripe-go/v74 v74.29.0
github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/xorm-io/builder v0.3.13
github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.11.0
golang.org/x/net v0.13.0
golang.org/x/oauth2 v0.10.0
golang.org/x/crypto v0.12.0
golang.org/x/net v0.14.0
golang.org/x/oauth2 v0.11.0
golang.org/x/text v0.13.0 // indirect
google.golang.org/api v0.138.0
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0 // indirect
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
maunium.net/go/mautrix v0.16.0
modernc.org/sqlite v1.18.2
)

532
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,15 @@ func TestGenerateI18nFrontend(t *testing.T) {
applyToOtherLanguage("frontend", "it", data)
applyToOtherLanguage("frontend", "ms", data)
applyToOtherLanguage("frontend", "tr", data)
applyToOtherLanguage("frontend", "ar", data)
applyToOtherLanguage("frontend", "he", data)
applyToOtherLanguage("frontend", "nl", data)
applyToOtherLanguage("frontend", "pl", data)
applyToOtherLanguage("frontend", "fi", data)
applyToOtherLanguage("frontend", "sv", data)
applyToOtherLanguage("frontend", "uk", data)
applyToOtherLanguage("frontend", "kk", data)
applyToOtherLanguage("frontend", "fa", data)
}
func TestGenerateI18nBackend(t *testing.T) {
@ -55,4 +64,13 @@ func TestGenerateI18nBackend(t *testing.T) {
applyToOtherLanguage("backend", "it", data)
applyToOtherLanguage("backend", "ms", data)
applyToOtherLanguage("backend", "tr", data)
applyToOtherLanguage("backend", "ar", data)
applyToOtherLanguage("backend", "he", data)
applyToOtherLanguage("backend", "nl", data)
applyToOtherLanguage("backend", "pl", data)
applyToOtherLanguage("backend", "fi", data)
applyToOtherLanguage("backend", "sv", data)
applyToOtherLanguage("backend", "uk", data)
applyToOtherLanguage("backend", "kk", data)
applyToOtherLanguage("backend", "fa", data)
}

142
i18n/locales/ar/data.json Normal file
View File

@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"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 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",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

142
i18n/locales/fa/data.json Normal file
View File

@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"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 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",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

142
i18n/locales/fi/data.json Normal file
View File

@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"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 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",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@ -19,7 +19,7 @@
"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",
"Unknown authentication type (not password or provider), form = %s": "Type d'authentification inconnu (pas de mot de passe ou de fournisseur), formulaire = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
"User's tag: %s is not listed in the application's tags": "Le tag de lutilisateur %s nest pas répertorié dans les tags de lapplication"
},
"cas": {
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
@ -43,7 +43,7 @@
"Phone number is invalid": "Le numéro de téléphone est invalide",
"Session outdated, please login again": "Session expirée, veuillez vous connecter à nouveau",
"The user is forbidden to sign in, please contact the administrator": "L'utilisateur est interdit de se connecter, veuillez contacter l'administrateur",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The user: %s doesn't exist in LDAP server": "L'utilisateur %s n'existe pas sur le serveur LDAP",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Le nom d'utilisateur ne peut contenir que des caractères alphanumériques, des traits soulignés ou des tirets, ne peut pas avoir de tirets ou de traits soulignés consécutifs et ne peut pas commencer ou se terminer par un tiret ou un trait souligné.",
"Username already exists": "Nom d'utilisateur existe déjà",
"Username cannot be an email address": "Nom d'utilisateur ne peut pas être une adresse e-mail",
@ -53,7 +53,7 @@
"Username must have at least 2 characters": "Le nom d'utilisateur doit comporter au moins 2 caractères",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Vous avez entré le mauvais mot de passe ou code plusieurs fois, veuillez attendre %d minutes et réessayer",
"Your region is not allow to signup by phone": "Votre région n'est pas autorisée à s'inscrire par téléphone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect": "mot de passe ou code invalide",
"password or code is incorrect, you have %d remaining chances": "Le mot de passe ou le code est incorrect, il vous reste %d chances",
"unsupported password type: %s": "Type de mot de passe non pris en charge : %s"
},
@ -61,8 +61,8 @@
"Missing parameter": "Paramètre manquant",
"Please login first": "Veuillez d'abord vous connecter",
"The user: %s doesn't exist": "L'utilisateur : %s n'existe pas",
"don't support captchaProvider: ": "Ne pas prendre en charge la captchaProvider",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
"don't support captchaProvider: ": "ne prend pas en charge captchaProvider: ",
"this operation is not allowed in demo mode": "cette opération nest pas autorisée en mode démo"
},
"ldap": {
"Ldap server exist": "Le serveur LDAP existe"

142
i18n/locales/he/data.json Normal file
View File

@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"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 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",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",

142
i18n/locales/kk/data.json Normal file
View File

@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"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 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",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",

142
i18n/locales/nl/data.json Normal file
View File

@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"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 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",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

142
i18n/locales/pl/data.json Normal file
View File

@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"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 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",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

142
i18n/locales/sv/data.json Normal file
View File

@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"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 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",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@ -24,14 +24,6 @@
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"chat": {
"The chat type must be \\\"AI\\\"": "The chat type must be \\\"AI\\\"",
"The chat: %s is not found": "The chat: %s is not found",
"The message is invalid": "The message is invalid",
"The message: %s is not found": "The message: %s is not found",
"The provider: %s is invalid": "The provider: %s is invalid",
"The provider: %s is not found": "The provider: %s is not found"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",

142
i18n/locales/uk/data.json Normal file
View File

@ -0,0 +1,142 @@
{
"account": {
"Failed to add user": "Failed to add user",
"Get init score failed, error: %w": "Get init score failed, error: %w",
"Please sign out first": "Please sign out first",
"The application does not allow to sign up new account": "The application does not allow to sign up new account"
},
"auth": {
"Challenge method should be S256": "Challenge method should be S256",
"Failed to create user, user information is invalid: %s": "Failed to create user, user information is invalid: %s",
"Failed to login in: %s": "Failed to login in: %s",
"Invalid token": "Invalid token",
"State expected: %s, but got: %s": "State expected: %s, but got: %s",
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up": "The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %%s, please use another way to sign up",
"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 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",
"Unknown authentication type (not password or provider), form = %s": "Unknown authentication type (not password or provider), form = %s",
"User's tag: %s is not listed in the application's tags": "User's tag: %s is not listed in the application's tags"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"Affiliation cannot be blank": "Affiliation cannot be blank",
"DisplayName cannot be blank": "DisplayName cannot be blank",
"DisplayName is not valid real name": "DisplayName is not valid real name",
"Email already exists": "Email already exists",
"Email cannot be empty": "Email cannot be empty",
"Email is invalid": "Email is invalid",
"Empty username.": "Empty username.",
"FirstName cannot be blank": "FirstName cannot be blank",
"LDAP user name or password incorrect": "LDAP user name or password incorrect",
"LastName cannot be blank": "LastName cannot be blank",
"Multiple accounts with same uid, please check your ldap server": "Multiple accounts with same uid, please check your ldap server",
"Organization does not exist": "Organization does not exist",
"Password must have at least 6 characters": "Password must have at least 6 characters",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Session outdated, please login again": "Session outdated, please login again",
"The user is forbidden to sign in, please contact the administrator": "The user is forbidden to sign in, please contact the administrator",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.",
"Username already exists": "Username already exists",
"Username cannot be an email address": "Username cannot be an email address",
"Username cannot contain white spaces": "Username cannot contain white spaces",
"Username cannot start with a digit": "Username cannot start with a digit",
"Username is too long (maximum is 39 characters).": "Username is too long (maximum is 39 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "You have entered the wrong password or code too many times, please wait for %d minutes and try again",
"Your region is not allow to signup by phone": "Your region is not allow to signup by phone",
"password or code is incorrect": "password or code is incorrect",
"password or code is incorrect, you have %d remaining chances": "password or code is incorrect, you have %d remaining chances",
"unsupported password type: %s": "unsupported password type: %s"
},
"general": {
"Missing parameter": "Missing parameter",
"Please login first": "Please login first",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
},
"ldap": {
"Ldap server exist": "Ldap server exist"
},
"link": {
"Please link first": "Please link first",
"This application has no providers": "This application has no providers",
"This application has no providers of type": "This application has no providers of type",
"This provider can't be unlinked": "This provider can't be unlinked",
"You are not the global admin, you can't unlink other users": "You are not the global admin, you can't unlink other users",
"You can't unlink yourself, you are not a member of any application": "You can't unlink yourself, you are not a member of any application"
},
"organization": {
"Only admin can modify the %s.": "Only admin can modify the %s.",
"The %s is immutable.": "The %s is immutable.",
"Unknown modify rule %s.": "Unknown modify rule %s."
},
"provider": {
"Invalid application id": "Invalid application id",
"the provider: %s does not exist": "the provider: %s does not exist"
},
"resource": {
"User is nil for tag: avatar": "User is nil for tag: avatar",
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Username or fullFilePath is empty: username = %s, fullFilePath = %s"
},
"saml": {
"Application %s not found": "Application %s not found"
},
"saml_sp": {
"provider %s's category is not SAML": "provider %s's category is not SAML"
},
"service": {
"Empty parameters for emailForm: %v": "Empty parameters for emailForm: %v",
"Invalid Email receivers: %s": "Invalid Email receivers: %s",
"Invalid phone receivers: %s": "Invalid phone receivers: %s"
},
"storage": {
"The objectKey: %s is not allowed": "The objectKey: %s is not allowed",
"The provider type: %s is not supported": "The provider type: %s is not supported"
},
"token": {
"Empty clientId or clientSecret": "Empty clientId or clientSecret",
"Grant_type: %s is not supported in this application": "Grant_type: %s is not supported in this application",
"Invalid application or wrong clientSecret": "Invalid application or wrong clientSecret",
"Invalid client_id": "Invalid client_id",
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "Redirect URI: %s doesn't exist in the allowed Redirect URI list",
"Token not found, invalid accessToken": "Token not found, invalid accessToken"
},
"user": {
"Display name cannot be empty": "Display name cannot be empty",
"New password cannot contain blank space.": "New password cannot contain blank space."
},
"user_upload": {
"Failed to import users": "Failed to import users"
},
"util": {
"No application is found for userId: %s": "No application is found for userId: %s",
"No provider for category: %s is found for application: %s": "No provider for category: %s is found for application: %s",
"The provider: %s is not found": "The provider: %s is not found"
},
"verification": {
"Code has not been sent yet!": "Code has not been sent yet!",
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"Turing test failed.": "Turing test failed.",
"Unable to get the email modify rule.": "Unable to get the email modify rule.",
"Unable to get the phone modify rule.": "Unable to get the phone modify rule.",
"Unknown type": "Unknown type",
"Wrong verification code!": "Wrong verification code!",
"You should verify your code in %d min!": "You should verify your code in %d min!",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

View File

@ -43,7 +43,7 @@
"Phone number is invalid": "无效手机号",
"Session outdated, please login again": "会话已过期,请重新登录",
"The user is forbidden to sign in, please contact the administrator": "该用户被禁止登录,请联系管理员",
"The user: %s doesn't exist in LDAP server": "The user: %s doesn't exist in LDAP server",
"The user: %s doesn't exist in LDAP server": "用户: %s 在LDAP服务器中未找到",
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "用户名只能包含字母数字字符、下划线或连字符,不能有连续的连字符或下划线,也不能以连字符或下划线开头或结尾",
"Username already exists": "用户名已存在",
"Username cannot be an email address": "用户名不可以是邮箱地址",
@ -62,7 +62,7 @@
"Please login first": "请先登录",
"The user: %s doesn't exist": "用户: %s不存在",
"don't support captchaProvider: ": "不支持验证码提供商: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode"
"this operation is not allowed in demo mode": "demo模式下不允许该操作"
},
"ldap": {
"Ldap server exist": "LDAP服务器已存在"

View File

@ -19,7 +19,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
@ -84,6 +83,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
payload.Set("code", code)
payload.Set("grant_type", "authorization_code")
payload.Set("client_id", idp.Config.ClientID)
payload.Set("client_secret", idp.Config.ClientSecret)
payload.Set("redirect_uri", idp.Config.RedirectURL)
resp, err := idp.Client.PostForm(idp.Config.Endpoint.TokenURL, payload)
if err != nil {
@ -118,11 +118,25 @@ func (idp *AdfsIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
if err != nil {
return nil, err
}
body, err := ioutil.ReadAll(resp.Body)
keyset, err := jwk.ParseKey(body)
body, err := io.ReadAll(resp.Body)
var respKeys struct {
Keys []interface{} `json:"keys"`
}
if err := json.Unmarshal(body, &respKeys); err != nil {
return nil, err
}
respKey, err := json.Marshal(&(respKeys.Keys[0]))
if err != nil {
return nil, err
}
keyset, err := jwk.ParseKey(respKey)
if err != nil {
return nil, err
}
tokenSrc := []byte(token.AccessToken)
publicKey, _ := keyset.PublicKey()
idToken, _ := jwt.Parse(tokenSrc, jwt.WithVerify(jwa.RS256, publicKey))

View File

@ -72,13 +72,13 @@ type FacebookCheckToken struct {
}
// FacebookCheckTokenData
// Get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#checktoken
// Get more detail via: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow#checktoken
type FacebookCheckTokenData struct {
UserId string `json:"user_id"`
}
// GetToken use code get access_token (*operation of getting code ought to be done in front)
// get more detail via: https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#confirm
// get more detail via: https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow#confirm
func (idp *FacebookIdProvider) GetToken(code string) (*oauth2.Token, error) {
params := url.Values{}
params.Add("client_id", idp.Config.ClientID)

View File

@ -19,6 +19,7 @@ import (
"net/http"
"net/url"
"reflect"
"strings"
"time"
"github.com/casdoor/casdoor/util"
@ -88,7 +89,7 @@ type GothIdProvider struct {
Session goth.Session
}
func NewGothIdProvider(providerType string, clientId string, clientSecret string, redirectUrl string, hostUrl string) *GothIdProvider {
func NewGothIdProvider(providerType string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, redirectUrl string, hostUrl string) (*GothIdProvider, error) {
var idp GothIdProvider
switch providerType {
case "Amazon":
@ -97,8 +98,27 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
Session: &amazon.Session{},
}
case "Apple":
if !strings.Contains(redirectUrl, "/api/callback") {
redirectUrl = strings.Replace(redirectUrl, "/callback", "/api/callback", 1)
}
iat := time.Now().Unix()
exp := iat + 60*60
sp := apple.SecretParams{
ClientId: clientId,
TeamId: clientSecret,
KeyId: clientId2,
PKCS8PrivateKey: clientSecret2,
Iat: int(iat),
Exp: int(exp),
}
secret, err := apple.MakeSecret(sp)
if err != nil {
return nil, err
}
idp = GothIdProvider{
Provider: apple.New(clientId, clientSecret, redirectUrl, nil),
Provider: apple.New(clientId, *secret, redirectUrl, nil),
Session: &apple.Session{},
}
case "AzureAD":
@ -382,17 +402,19 @@ func NewGothIdProvider(providerType string, clientId string, clientSecret string
Session: &zoom.Session{},
}
default:
return nil
return nil, fmt.Errorf("OAuth Goth provider type: %s is not supported", providerType)
}
return &idp
return &idp, nil
}
// SetHttpClient
// Goth's idp all implement the Client method, but since the goth.Provider interface does not provide to modify idp's client method, reflection is required
func (idp *GothIdProvider) SetHttpClient(client *http.Client) {
idpClient := reflect.ValueOf(idp.Provider).Elem().FieldByName("HTTPClient")
idpClient.Set(reflect.ValueOf(client))
if idpClient.IsValid() {
idpClient.Set(reflect.ValueOf(client))
}
}
func (idp *GothIdProvider) GetToken(code string) (*oauth2.Token, error) {
@ -468,6 +490,8 @@ func getUser(gothUser goth.User, provider string) *UserInfo {
if provider == "steam" {
user.Username = user.Id
user.Email = ""
} else if provider == "apple" {
user.Username = util.GetUsernameFromEmail(user.Email)
}
return &user
}

View File

@ -24,20 +24,10 @@ import (
"golang.org/x/oauth2"
)
const Web3AuthTokenKey = "web3AuthToken"
type MetaMaskIdProvider struct {
Client *http.Client
}
type Web3AuthToken struct {
Address string `json:"address"`
Nonce string `json:"nonce"`
CreateAt uint64 `json:"createAt"`
TypedData string `json:"typedData"`
Signature string `json:"signature"` // signature for typed data
}
func NewMetaMaskIdProvider() *MetaMaskIdProvider {
idp := &MetaMaskIdProvider{}
return idp

View File

@ -15,6 +15,7 @@
package idp
import (
"fmt"
"net/http"
"strings"
@ -33,13 +34,15 @@ type UserInfo struct {
}
type ProviderInfo struct {
Type string
SubType string
ClientId string
ClientSecret string
AppId string
HostUrl string
RedirectUrl string
Type string
SubType string
ClientId string
ClientSecret string
ClientId2 string
ClientSecret2 string
AppId string
HostUrl string
RedirectUrl string
TokenURL string
AuthURL string
@ -53,69 +56,71 @@ type IdProvider interface {
GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) IdProvider {
func GetIdProvider(idpInfo *ProviderInfo, redirectUrl string) (IdProvider, error) {
switch idpInfo.Type {
case "GitHub":
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewGithubIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Google":
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewGoogleIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "QQ":
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewQqIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "WeChat":
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewWeChatIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Facebook":
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewFacebookIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "DingTalk":
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewDingTalkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Weibo":
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewWeiBoIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Gitee":
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewGiteeIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "LinkedIn":
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewLinkedInIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "WeCom":
if idpInfo.SubType == "Internal" {
return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewWeComInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
} else if idpInfo.SubType == "Third-party" {
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewWeComIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
} else {
return nil
return nil, fmt.Errorf("WeCom provider subType: %s is not supported", idpInfo.SubType)
}
case "Lark":
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewLarkIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "GitLab":
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
case "Adfs":
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
return NewGitlabIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "ADFS":
return NewAdfsIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Baidu":
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewBaiduIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Alipay":
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewAlipayIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Custom":
return NewCustomIdProvider(idpInfo, redirectUrl)
return NewCustomIdProvider(idpInfo, redirectUrl), nil
case "Infoflow":
if idpInfo.SubType == "Internal" {
return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
return NewInfoflowInternalIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
} else if idpInfo.SubType == "Third-party" {
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl)
return NewInfoflowIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.AppId, redirectUrl), nil
} else {
return nil
return nil, fmt.Errorf("Infoflow provider subType: %s is not supported", idpInfo.SubType)
}
case "Casdoor":
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
return NewCasdoorIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Okta":
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
return NewOktaIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl), nil
case "Douyin":
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewDouyinIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "Bilibili":
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl)
return NewBilibiliIdProvider(idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl), nil
case "MetaMask":
return NewMetaMaskIdProvider()
return NewMetaMaskIdProvider(), nil
case "Web3Onboard":
return NewWeb3OnboardIdProvider(), nil
default:
if isGothSupport(idpInfo.Type) {
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, redirectUrl, idpInfo.HostUrl)
return NewGothIdProvider(idpInfo.Type, idpInfo.ClientId, idpInfo.ClientSecret, idpInfo.ClientId2, idpInfo.ClientSecret2, redirectUrl, idpInfo.HostUrl)
}
return nil
return nil, fmt.Errorf("OAuth provider type: %s is not supported", idpInfo.Type)
}
}

103
idp/web3onboard.go Normal file
View File

@ -0,0 +1,103 @@
// 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 idp
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"time"
"golang.org/x/oauth2"
)
const Web3AuthTokenKey = "web3AuthToken"
type Web3AuthToken struct {
Address string `json:"address"`
Nonce string `json:"nonce"`
CreateAt uint64 `json:"createAt"`
TypedData string `json:"typedData"` // typed data use for application
Signature string `json:"signature"` // signature for typed data
WalletType string `json:"walletType"` // e.g."MetaMask", "Coinbase"
}
type Web3OnboardIdProvider struct {
Client *http.Client
}
func NewWeb3OnboardIdProvider() *Web3OnboardIdProvider {
idp := &Web3OnboardIdProvider{}
return idp
}
func (idp *Web3OnboardIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *Web3OnboardIdProvider) GetToken(code string) (*oauth2.Token, error) {
web3AuthToken := Web3AuthToken{}
if err := json.Unmarshal([]byte(code), &web3AuthToken); err != nil {
return nil, err
}
token := &oauth2.Token{
AccessToken: fmt.Sprintf("%v:%v", Web3AuthTokenKey, web3AuthToken.Address),
TokenType: "Bearer",
Expiry: time.Now().AddDate(0, 1, 0),
}
token = token.WithExtra(map[string]interface{}{
Web3AuthTokenKey: web3AuthToken,
})
return token, nil
}
func (idp *Web3OnboardIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
web3AuthToken, ok := token.Extra(Web3AuthTokenKey).(Web3AuthToken)
if !ok {
return nil, errors.New("invalid web3AuthToken")
}
fmtAddress := fmt.Sprintf("%v_%v",
strings.ReplaceAll(strings.TrimSpace(web3AuthToken.WalletType), " ", "_"),
web3AuthToken.Address,
)
userInfo := &UserInfo{
Id: fmtAddress,
Username: fmtAddress,
DisplayName: fmtAddress,
AvatarUrl: fmt.Sprintf("metamask:%v", forceEthereumAddress(web3AuthToken.Address)),
}
return userInfo, nil
}
func forceEthereumAddress(address string) string {
// The required address to general MetaMask avatar is a string of length 42 that represents an Ethereum address.
// This function is used to force any address as an Ethereum address
address = strings.TrimSpace(address)
var builder strings.Builder
for _, ch := range address {
builder.WriteRune(ch)
}
for len(builder.String()) < 42 {
builder.WriteString("0")
}
if len(builder.String()) > 42 {
return builder.String()[:42]
}
return builder.String()
}

View File

@ -9,12 +9,13 @@
"passwordType": "plain",
"passwordSalt": "",
"passwordOptions": ["AtLeast6"],
"countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR"],
"countryCodes": ["US", "GB", "ES", "FR", "DE", "CN", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR", "DZ", "IL", "PH", "NL", "PL", "FI", "SE", "UA", "KZ"],
"defaultAvatar": "",
"defaultApplication": "",
"tags": [],
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr"],
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
"masterPassword": "",
"defaultPassword": "",
"initScore": 2000,
"enableSoftDeletion": false,
"isProfilePublic": true,
@ -123,7 +124,6 @@
"score": 2000,
"ranking": 1,
"isAdmin": true,
"isGlobalAdmin": true,
"isForbidden": false,
"isDeleted": false,
"signupApplication": "",
@ -177,9 +177,7 @@
],
"permissions": [
{
"actions": [
""
],
"actions": [],
"displayName": "",
"effect": "",
"isEnabled": true,
@ -187,15 +185,9 @@
"name": "",
"owner": "",
"resourceType": "",
"resources": [
""
],
"roles": [
""
],
"users": [
""
]
"resources": [],
"roles": [],
"users": []
}
],
"payments": [
@ -237,9 +229,7 @@
"name": "",
"owner": "",
"price": 0,
"providers": [
""
],
"providers": [],
"quantity": 0,
"returnUrl": "",
"sold": 0,
@ -269,12 +259,8 @@
"isEnabled": true,
"name": "",
"owner": "",
"roles": [
""
],
"users": [
""
]
"roles": [],
"users": []
}
],
"syncers": [
@ -285,7 +271,7 @@
"databaseType": "",
"errorText": "",
"host": "",
"isEnabled": true,
"isEnabled": false,
"name": "",
"organization": "",
"owner": "",
@ -299,9 +285,7 @@
"isHashed": true,
"name": "",
"type": "",
"values": [
""
]
"values": []
}
],
"tablePrimaryKey": "",
@ -331,9 +315,7 @@
"webhooks": [
{
"contentType": "",
"events": [
""
],
"events": [],
"headers": [
{
"name": "",

View File

@ -25,6 +25,11 @@ import (
)
func StartLdapServer() {
ldapServerPort := conf.GetConfigString("ldapServerPort")
if ldapServerPort == "" || ldapServerPort == "0" {
return
}
server := ldap.NewServer()
routes := ldap.NewRouteMux()
@ -32,9 +37,9 @@ func StartLdapServer() {
routes.Search(handleSearch).Label(" SEARCH****")
server.Handle(routes)
err := server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
err := server.ListenAndServe("0.0.0.0:" + ldapServerPort)
if err != nil {
log.Printf("StartLdapServer() failed, ErrMsg = %s", err.Error())
log.Printf("StartLdapServer() failed, err = %s", err.Error())
}
}
@ -62,7 +67,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
return
}
if bindOrg == "built-in" || bindUser.IsGlobalAdmin {
if bindOrg == "built-in" || bindUser.IsGlobalAdmin() {
m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true
} else if bindUser.IsAdmin {
m.Client.IsOrgAdmin = true

View File

@ -117,7 +117,7 @@ func GetFilteredUsers(m *ldap.Message) (filteredUsers []*object.User, code int)
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, "en")
if !hasPermission {
log.Printf("ErrMsg = %v", err.Error())
log.Printf("err = %v", err.Error())
return nil, ldap.LDAPResultInsufficientAccessRights
}

18
main.go
View File

@ -15,7 +15,6 @@
package main
import (
"flag"
"fmt"
"github.com/beego/beego"
@ -26,22 +25,15 @@ import (
"github.com/casdoor/casdoor/ldap"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/radius"
"github.com/casdoor/casdoor/routers"
"github.com/casdoor/casdoor/util"
)
func getCreateDatabaseFlag() bool {
res := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
flag.Parse()
return *res
}
func main() {
createDatabase := getCreateDatabaseFlag()
object.InitAdapter(createDatabase)
object.CreateTables(createDatabase)
object.DoMigration()
object.InitFlag()
object.InitAdapter()
object.CreateTables()
object.InitDb()
object.InitFromFile()
@ -50,6 +42,7 @@ func main() {
proxy.InitHttpClient()
authz.InitApi()
object.InitUserManager()
object.InitCasvisorConfig()
util.SafeGoroutine(func() { object.RunSyncUsersJob() })
@ -88,6 +81,7 @@ func main() {
logs.SetLogFuncCall(false)
go ldap.StartLdapServer()
go radius.StartRadiusServer()
go object.ClearThroughputPerSecond()
beego.Run(fmt.Sprintf(":%v", port))

View File

@ -22,14 +22,15 @@ config: |
dataSourceName = "file:ent?mode=memory&cache=shared&_fk=1"
dbName = casdoor
redisEndpoint =
defaultStorageProvider =
defaultStorageProvider =
isCloudIntranet = false
authState = "casdoor"
socks5Proxy = ""
verificationCodeTimeout = 10
initScore = 2000
initScore = 0
logPostOnly = true
origin = "https://door.casbin.com"
origin =
enableGzip = true
imagePullSecrets: []
nameOverride: ""

29
notification/bark.go Normal file
View File

@ -0,0 +1,29 @@
// 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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/bark"
)
func NewBarkProvider(deviceKey string) (notify.Notifier, error) {
barkSrv := bark.New(deviceKey)
notifier := notify.New()
notifier.UseServices(barkSrv)
return notifier, nil
}

View File

@ -12,37 +12,35 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package object
package notification
import (
"bytes"
"context"
"fmt"
"net/http"
"github.com/casdoor/casdoor/proxy"
)
type HttpSmsClient struct {
type HttpNotificationClient struct {
endpoint string
method string
paramName string
text string
}
func newHttpSmsClient(endpoint string, method string, paramName string, text string) (*HttpSmsClient, error) {
client := &HttpSmsClient{
func NewCustomHttpProvider(endpoint string, method string, paramName string) (*HttpNotificationClient, error) {
client := &HttpNotificationClient{
endpoint: endpoint,
method: method,
paramName: paramName,
text: text,
}
return client, nil
}
func (c *HttpSmsClient) SendMessage(param map[string]string, targetPhoneNumber ...string) error {
func (c *HttpNotificationClient) Send(ctx context.Context, subject string, content string) error {
var err error
content := param["code"]
httpClient := proxy.DefaultHttpClient
req, err := http.NewRequest(c.method, c.endpoint, bytes.NewBufferString(content))
@ -68,7 +66,7 @@ func (c *HttpSmsClient) SendMessage(param map[string]string, targetPhoneNumber .
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("SendMessage() error, custom HTTP SMS request failed with status: %s", resp.Status)
return fmt.Errorf("SendMessage() error, custom HTTP Notification request failed with status: %s", resp.Status)
}
return err

33
notification/dingtalk.go Normal file
View File

@ -0,0 +1,33 @@
// 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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/dingding"
)
func NewDingTalkProvider(token string, secret string) (notify.Notifier, error) {
cfg := dingding.Config{
Token: token,
Secret: secret,
}
dingtalkSrv := dingding.New(&cfg)
notifier := notify.New()
notifier.UseServices(dingtalkSrv)
return notifier, nil
}

37
notification/discord.go Normal file
View File

@ -0,0 +1,37 @@
// 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 notification
import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/discord"
)
func NewDiscordProvider(token string, channelId string) (*notify.Notify, error) {
discordSrv := discord.New()
err := discordSrv.AuthenticateWithBotToken(token)
if err != nil {
return nil, err
}
discordSrv.SetHttpClient(proxy.ProxyHttpClient)
discordSrv.AddReceivers(channelId)
notifier := notify.NewWithServices(discordSrv)
return notifier, nil
}

View File

@ -0,0 +1,53 @@
// 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 notification
import (
"context"
"strings"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/googlechat"
"google.golang.org/api/chat/v1"
"google.golang.org/api/option"
)
func NewGoogleChatProvider(credentials string) (*notify.Notify, error) {
withCred := option.WithCredentialsJSON([]byte(credentials))
withSpacesScope := option.WithScopes("https://www.googleapis.com/auth/chat.spaces")
listSvc, err := chat.NewService(context.Background(), withCred, withSpacesScope)
spaces, err := listSvc.Spaces.List().Do()
if err != nil {
return nil, err
}
receivers := make([]string, 0)
for _, space := range spaces.Spaces {
name := strings.Replace(space.Name, "spaces/", "", 1)
receivers = append(receivers, name)
}
googleChatSrv, err := googlechat.New(withCred)
if err != nil {
return nil, err
}
googleChatSrv.AddReceivers(receivers...)
notifier := notify.NewWithServices(googleChatSrv)
return notifier, nil
}

29
notification/lark.go Normal file
View File

@ -0,0 +1,29 @@
// 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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/lark"
)
func NewLarkProvider(webhookURL string) (notify.Notifier, error) {
larkSrv := lark.NewWebhookService(webhookURL)
notifier := notify.New()
notifier.UseServices(larkSrv)
return notifier, nil
}

32
notification/line.go Normal file
View File

@ -0,0 +1,32 @@
// 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 notification
import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/line"
)
func NewLineProvider(channelSecret string, accessToken string, receiver string) (*notify.Notify, error) {
lineSrv, _ := line.NewWithHttpClient(channelSecret, accessToken, proxy.ProxyHttpClient)
lineSrv.AddReceivers(receiver)
notifier := notify.New()
notifier.UseServices(lineSrv)
return notifier, nil
}

36
notification/matrix.go Normal file
View File

@ -0,0 +1,36 @@
// 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 notification
import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/matrix"
"maunium.net/go/mautrix/id"
)
func NewMatrixProvider(userId string, accessToken string, roomId string, homeServer string) (*notify.Notify, error) {
matrixSrv, err := matrix.New(id.UserID(userId), id.RoomID(roomId), homeServer, accessToken)
if err != nil {
return nil, err
}
matrixSrv.SetHttpClient(proxy.ProxyHttpClient)
notifier := notify.New()
notifier.UseServices(matrixSrv)
return notifier, nil
}

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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/msteams"
)
func NewMicrosoftTeamsProvider(webhookURL string) (notify.Notifier, error) {
msTeamsSrv := msteams.New()
msTeamsSrv.AddReceivers(webhookURL)
notifier := notify.New()
notifier.UseServices(msTeamsSrv)
return notifier, nil
}

59
notification/provider.go Normal file
View File

@ -0,0 +1,59 @@
// 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 notification
import "github.com/casdoor/notify"
func GetNotificationProvider(typ string, clientId string, clientSecret string, clientId2 string, clientSecret2 string, appId string, receiver string, method string, title string, metaData string) (notify.Notifier, error) {
if typ == "Telegram" {
return NewTelegramProvider(clientSecret, receiver)
} else if typ == "Custom HTTP" {
return NewCustomHttpProvider(receiver, method, title)
} else if typ == "DingTalk" {
return NewDingTalkProvider(clientId, clientSecret)
} else if typ == "Lark" {
return NewLarkProvider(clientSecret)
} else if typ == "Microsoft Teams" {
return NewMicrosoftTeamsProvider(clientSecret)
} else if typ == "Bark" {
return NewBarkProvider(clientSecret)
} else if typ == "Pushover" {
return NewPushoverProvider(clientSecret, receiver)
} else if typ == "Pushbullet" {
return NewPushbulletProvider(clientSecret, receiver)
} else if typ == "Slack" {
return NewSlackProvider(clientSecret, receiver)
} else if typ == "Webpush" {
return NewWebpushProvider(clientId, clientSecret, receiver)
} else if typ == "Discord" {
return NewDiscordProvider(clientSecret, receiver)
} else if typ == "Google Chat" {
return NewGoogleChatProvider(metaData)
} else if typ == "Line" {
return NewLineProvider(clientSecret, appId, receiver)
} else if typ == "Matrix" {
return NewMatrixProvider(clientId, clientSecret, appId, receiver)
} else if typ == "Twitter" {
return NewTwitterProvider(clientId, clientSecret, clientId2, clientSecret2, receiver)
} else if typ == "Reddit" {
return NewRedditProvider(clientId, clientSecret, clientId2, clientSecret2, receiver)
} else if typ == "Rocket Chat" {
return NewRocketChatProvider(clientId, clientSecret, appId, receiver)
} else if typ == "Viber" {
return NewViberProvider(clientId, clientSecret, appId, receiver)
}
return nil, nil
}

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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/pushbullet"
)
func NewPushbulletProvider(apiToken string, deviceNickname string) (notify.Notifier, error) {
pushbulletSrv := pushbullet.New(apiToken)
pushbulletSrv.AddReceivers(deviceNickname)
notifier := notify.New()
notifier.UseServices(pushbulletSrv)
return notifier, nil
}

31
notification/pushover.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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/pushover"
)
func NewPushoverProvider(appToken string, recipientID string) (notify.Notifier, error) {
pushoverSrv := pushover.New(appToken)
pushoverSrv.AddReceivers(recipientID)
notifier := notify.New()
notifier.UseServices(pushoverSrv)
return notifier, nil
}

34
notification/reddit.go Normal file
View File

@ -0,0 +1,34 @@
// 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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/reddit"
)
func NewRedditProvider(clientId string, clientSecret string, username string, password string, recipient string) (notify.Notifier, error) {
redditSrv, err := reddit.New(clientId, clientSecret, username, password)
if err != nil {
return nil, err
}
redditSrv.AddReceivers(recipient)
notifier := notify.New()
notifier.UseServices(redditSrv)
return notifier, nil
}

View File

@ -0,0 +1,47 @@
// 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 notification
import (
"fmt"
"strings"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/rocketchat"
)
func NewRocketChatProvider(clientId string, clientSecret string, endpoint string, channelName string) (notify.Notifier, error) {
parts := strings.Split(endpoint, "://")
var scheme, serverURL string
if len(parts) >= 2 {
scheme = parts[0]
serverURL = parts[1]
} else {
return nil, fmt.Errorf("parse endpoint error")
}
rocketChatSrv, err := rocketchat.New(serverURL, scheme, clientId, clientSecret)
if err != nil {
return nil, err
}
rocketChatSrv.AddReceivers(channelName)
notifier := notify.New()
notifier.UseServices(rocketChatSrv)
return notifier, nil
}

30
notification/slack.go Normal file
View File

@ -0,0 +1,30 @@
// 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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/slack"
)
func NewSlackProvider(apiToken string, channelID string) (*notify.Notify, error) {
slackSrv := slack.New(apiToken)
slackSrv.AddReceivers(channelID)
notifier := notify.New()
notifier.UseServices(slackSrv)
return notifier, nil
}

45
notification/telegram.go Normal file
View File

@ -0,0 +1,45 @@
// 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 notification
import (
"strconv"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/telegram"
api "github.com/go-telegram-bot-api/telegram-bot-api"
)
func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, error) {
client, err := api.NewBotAPIWithClient(apiToken, proxy.ProxyHttpClient)
if err != nil {
return nil, err
}
telegramSrv := &telegram.Telegram{}
telegramSrv.SetClient(client)
chatId, err := strconv.ParseInt(chatIdStr, 10, 64)
if err != nil {
return nil, err
}
telegramSrv.AddReceivers(chatId)
notifier := notify.New()
notifier.UseServices(telegramSrv)
return notifier, nil
}

41
notification/twitter.go Normal file
View File

@ -0,0 +1,41 @@
// 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 notification
import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/twitter"
)
func NewTwitterProvider(consumerKey string, consumerSecret string, accessToken string, accessTokenSecret string, twitterId string) (*notify.Notify, error) {
credentials := twitter.Credentials{
ConsumerKey: consumerKey,
ConsumerSecret: consumerSecret,
AccessToken: accessToken,
AccessTokenSecret: accessTokenSecret,
}
twitterSrv, err := twitter.NewWithHttpClient(credentials, proxy.ProxyHttpClient)
if err != nil {
return nil, err
}
twitterSrv.AddReceivers(twitterId)
notifier := notify.New()
notifier.UseServices(twitterSrv)
return notifier, nil
}

36
notification/viber.go Normal file
View File

@ -0,0 +1,36 @@
// 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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/viber"
)
func NewViberProvider(senderName string, appKey string, webhookURL string, receiverId string) (notify.Notifier, error) {
viberSrv := viber.New(appKey, senderName, "")
err := viberSrv.SetWebhook(webhookURL)
if err != nil {
return nil, err
}
viberSrv.AddReceivers(receiverId)
notifier := notify.New()
notifier.UseServices(viberSrv)
return notifier, nil
}

33
notification/webpush.go Normal file
View File

@ -0,0 +1,33 @@
// 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 notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/webpush"
)
func NewWebpushProvider(publicKey string, privateKey string, endpoint string) (*notify.Notify, error) {
webpushSrv := webpush.New(publicKey, privateKey)
subscription := webpush.Subscription{
Endpoint: endpoint,
}
webpushSrv.AddReceivers(subscription)
notifier := notify.NewWithServices(webpushSrv)
return notifier, nil
}

View File

@ -18,7 +18,6 @@ import (
"fmt"
"strings"
"github.com/casbin/casbin/v2/model"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
@ -31,17 +30,15 @@ type Adapter struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Type string `xorm:"varchar(100)" json:"type"`
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"`
Database string `xorm:"varchar(100)" json:"database"`
Table string `xorm:"varchar(100)" json:"table"`
TableNamePrefix string `xorm:"varchar(100)" json:"tableNamePrefix"`
IsEnabled bool `json:"isEnabled"`
Table string `xorm:"varchar(100)" json:"table"`
UseSameDb bool `json:"useSameDb"`
Type string `xorm:"varchar(100)" json:"type"`
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"`
Database string `xorm:"varchar(100)" json:"database"`
*xormadapter.Adapter `xorm:"-" json:"-"`
}
@ -142,55 +139,69 @@ func (adapter *Adapter) GetId() string {
return fmt.Sprintf("%s/%s", adapter.Owner, adapter.Name)
}
func (adapter *Adapter) getTable() string {
if adapter.DatabaseType == "mssql" {
return fmt.Sprintf("[%s]", adapter.Table)
func (adapter *Adapter) InitAdapter() error {
if adapter.Adapter != nil {
return nil
}
var driverName string
var dataSourceName string
if adapter.UseSameDb || adapter.isBuiltIn() {
driverName = conf.GetConfigString("driverName")
dataSourceName = conf.GetConfigString("dataSourceName")
if conf.GetConfigString("driverName") == "mysql" {
dataSourceName = dataSourceName + conf.GetConfigString("dbName")
}
} else {
return adapter.Table
}
}
func (adapter *Adapter) initAdapter() error {
if adapter.Adapter == nil {
var dataSourceName string
if adapter.builtInAdapter() {
dataSourceName = conf.GetConfigString("dataSourceName")
if adapter.DatabaseType == "mysql" {
dataSourceName = dataSourceName + adapter.Database
}
} else {
switch adapter.DatabaseType {
case "mssql":
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "mysql":
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "postgres":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "CockroachDB":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s serial_normalization=virtual_sequence",
adapter.User, adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "sqlite3":
dataSourceName = fmt.Sprintf("file:%s", adapter.Host)
default:
return fmt.Errorf("unsupported database type: %s", adapter.DatabaseType)
}
}
if !isCloudIntranet {
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
}
var err error
engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName)
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix)
if err != nil {
return err
driverName = adapter.DatabaseType
switch driverName {
case "mssql":
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "mysql":
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "postgres":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", adapter.User,
adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "CockroachDB":
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s serial_normalization=virtual_sequence",
adapter.User, adapter.Password, adapter.Host, adapter.Port, adapter.Database)
case "sqlite3":
dataSourceName = fmt.Sprintf("file:%s", adapter.Host)
default:
return fmt.Errorf("unsupported database type: %s", adapter.DatabaseType)
}
}
if !isCloudIntranet {
dataSourceName = strings.ReplaceAll(dataSourceName, "dbi.", "db.")
}
engine, err := xorm.NewEngine(driverName, dataSourceName)
if err != nil {
return err
}
if (adapter.UseSameDb || adapter.isBuiltIn()) && driverName == "postgres" {
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
if schema != "" {
engine.SetSchema(schema)
}
}
var tableName string
if driverName == "mssql" {
tableName = fmt.Sprintf("[%s]", adapter.Table)
} else {
tableName = adapter.Table
}
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, tableName, "")
if err != nil {
return err
}
return nil
}
@ -214,130 +225,10 @@ func adapterChangeTrigger(oldName string, newName string) error {
return session.Commit()
}
func safeReturn(policy []string, i int) string {
if len(policy) > i {
return policy[i]
} else {
return ""
}
}
func matrixToCasbinRules(Ptype string, policies [][]string) []*xormadapter.CasbinRule {
res := []*xormadapter.CasbinRule{}
for _, policy := range policies {
line := xormadapter.CasbinRule{
Ptype: Ptype,
V0: safeReturn(policy, 0),
V1: safeReturn(policy, 1),
V2: safeReturn(policy, 2),
V3: safeReturn(policy, 3),
V4: safeReturn(policy, 4),
V5: safeReturn(policy, 5),
}
res = append(res, &line)
}
return res
}
func GetPolicies(adapter *Adapter) ([]*xormadapter.CasbinRule, error) {
err := adapter.initAdapter()
if err != nil {
return nil, err
}
casbinModel := getModelDef()
err = adapter.LoadPolicy(casbinModel)
if err != nil {
return nil, err
}
policies := matrixToCasbinRules("p", casbinModel.GetPolicy("p", "p"))
policies = append(policies, matrixToCasbinRules("g", casbinModel.GetPolicy("g", "g"))...)
return policies, nil
}
func UpdatePolicy(oldPolicy, newPolicy []string, adapter *Adapter) (bool, error) {
err := adapter.initAdapter()
if err != nil {
return false, err
}
casbinModel := getModelDef()
err = adapter.LoadPolicy(casbinModel)
if err != nil {
return false, err
}
affected := casbinModel.UpdatePolicy("p", "p", oldPolicy, newPolicy)
if err != nil {
return affected, err
}
err = adapter.SavePolicy(casbinModel)
if err != nil {
return false, err
}
return affected, nil
}
func AddPolicy(policy []string, adapter *Adapter) (bool, error) {
err := adapter.initAdapter()
if err != nil {
return false, err
}
casbinModel := getModelDef()
err = adapter.LoadPolicy(casbinModel)
if err != nil {
return false, err
}
casbinModel.AddPolicy("p", "p", policy)
err = adapter.SavePolicy(casbinModel)
if err != nil {
return false, err
}
return true, nil
}
func RemovePolicy(policy []string, adapter *Adapter) (bool, error) {
err := adapter.initAdapter()
if err != nil {
return false, err
}
casbinModel := getModelDef()
err = adapter.LoadPolicy(casbinModel)
if err != nil {
return false, err
}
affected := casbinModel.RemovePolicy("p", "p", policy)
if err != nil {
return affected, err
}
err = adapter.SavePolicy(casbinModel)
if err != nil {
return false, err
}
return affected, nil
}
func (adapter *Adapter) builtInAdapter() bool {
func (adapter *Adapter) isBuiltIn() bool {
if adapter.Owner != "built-in" {
return false
}
return adapter.Name == "user-adapter-built-in" || adapter.Name == "api-adapter-built-in"
}
func getModelDef() model.Model {
casbinModel := model.NewModel()
casbinModel.AddDef("p", "p", "_, _, _, _, _, _")
casbinModel.AddDef("g", "g", "_, _, _, _, _, _")
return casbinModel
}

View File

@ -25,11 +25,19 @@ import (
)
type SignupItem struct {
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Prompted bool `json:"prompted"`
Rule string `json:"rule"`
Name string `json:"name"`
Visible bool `json:"visible"`
Required bool `json:"required"`
Prompted bool `json:"prompted"`
Label string `json:"label"`
Placeholder string `json:"placeholder"`
Rule string `json:"rule"`
}
type SamlItem struct {
Name string `json:"name"`
NameFormat string `json:"nameformat"`
Value string `json:"value"`
}
type Application struct {
@ -49,15 +57,19 @@ type Application struct {
EnableAutoSignin bool `json:"enableAutoSignin"`
EnableCodeSignin bool `json:"enableCodeSignin"`
EnableSamlCompress bool `json:"enableSamlCompress"`
EnableSamlC14n10 bool `json:"enableSamlC14n10"`
EnableWebAuthn bool `json:"enableWebAuthn"`
EnableLinkWithEmail bool `json:"enableLinkWithEmail"`
OrgChoiceMode string `json:"orgChoiceMode"`
SamlReplyUrl string `xorm:"varchar(100)" json:"samlReplyUrl"`
Providers []*ProviderItem `xorm:"mediumtext" json:"providers"`
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
SignupItems []*SignupItem `xorm:"varchar(2000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
CertPublicKey string `xorm:"-" json:"certPublicKey"`
Tags []string `xorm:"mediumtext" json:"tags"`
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
SamlAttributes []*SamlItem `xorm:"varchar(1000)" json:"samlAttributes"`
ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
@ -304,6 +316,9 @@ func GetMaskedApplication(application *Application, userId string) *Application
if application.OrganizationObj.MasterPassword != "" {
application.OrganizationObj.MasterPassword = "***"
}
if application.OrganizationObj.DefaultPassword != "" {
application.OrganizationObj.DefaultPassword = "***"
}
if application.OrganizationObj.PasswordType != "" {
application.OrganizationObj.PasswordType = "***"
}
@ -311,6 +326,11 @@ func GetMaskedApplication(application *Application, userId string) *Application
application.OrganizationObj.PasswordSalt = "***"
}
}
if application.InvitationCodes != nil {
application.InvitationCodes = []string{"***"}
}
return application
}
@ -325,6 +345,35 @@ func GetMaskedApplications(applications []*Application, userId string) []*Applic
return applications
}
func GetAllowedApplications(applications []*Application, userId string) ([]*Application, error) {
if isUserIdGlobalAdmin(userId) {
return applications, nil
}
user, err := GetUser(userId)
if err != nil {
return nil, err
}
if user.IsAdmin {
return applications, nil
}
res := []*Application{}
for _, application := range applications {
var allowed bool
allowed, err = CheckLoginPermission(userId, application)
if err != nil {
return nil, err
}
if allowed {
res = append(res, application)
}
}
return res, nil
}
func UpdateApplication(id string, application *Application) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
oldApplication, err := getApplication(owner, name)
@ -421,15 +470,14 @@ func (application *Application) GetId() string {
}
func (application *Application) IsRedirectUriValid(redirectUri string) bool {
isValid := false
for _, targetUri := range application.RedirectUris {
redirectUris := append([]string{"http://localhost:", "https://localhost:", "http://127.0.0.1:", "http://casdoor-app"}, application.RedirectUris...)
for _, targetUri := range redirectUris {
targetUriRegex := regexp.MustCompile(targetUri)
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
isValid = true
break
return true
}
}
return isValid
return false
}
func IsOriginAllowed(origin string) (bool, error) {

View File

@ -33,10 +33,8 @@ type Cert struct {
BitSize int `json:"bitSize"`
ExpireInYears int `json:"expireInYears"`
Certificate string `xorm:"mediumtext" json:"certificate"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
AuthorityPublicKey string `xorm:"mediumtext" json:"authorityPublicKey"`
AuthorityRootPublicKey string `xorm:"mediumtext" json:"authorityRootPublicKey"`
Certificate string `xorm:"mediumtext" json:"certificate"`
PrivateKey string `xorm:"mediumtext" json:"privateKey"`
}
func GetMaskedCert(cert *Cert) *Cert {

BIN
object/cert.go~ Normal file

Binary file not shown.

View File

@ -66,8 +66,11 @@ func CheckUserSignup(application *Application, organization *Organization, form
}
}
if len(form.Password) <= 5 {
return i18n.Translate(lang, "check:Password must have at least 6 characters")
if application.IsSignupItemVisible("Password") {
msg := CheckPasswordComplexityByOrg(organization, form.Password)
if msg != "" {
return msg
}
}
if application.IsSignupItemVisible("Email") {
@ -124,6 +127,18 @@ func CheckUserSignup(application *Application, organization *Organization, form
}
}
if len(application.InvitationCodes) > 0 {
if form.InvitationCode == "" {
if application.IsSignupItemRequired("Invitation code") {
return i18n.Translate(lang, "check:Invitation code cannot be blank")
}
} else {
if !util.InSlice(application.InvitationCodes, form.InvitationCode) {
return i18n.Translate(lang, "check:Invitation code is invalid")
}
}
}
return ""
}
@ -141,7 +156,7 @@ func checkSigninErrorTimes(user *User, lang string) string {
// reset the error times
user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, user.IsGlobalAdmin)
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
}
return ""
@ -319,7 +334,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
if requestUser == nil {
return false, fmt.Errorf(i18n.Translate(lang, "check:Session outdated, please login again"))
}
if requestUser.IsGlobalAdmin {
if requestUser.IsGlobalAdmin() {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
@ -335,9 +350,9 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
return hasPermission, fmt.Errorf(i18n.Translate(lang, "auth:Unauthorized operation"))
}
func CheckAccessPermission(userId string, application *Application) (bool, error) {
var err error
if userId == "built-in/admin" {
func CheckLoginPermission(userId string, application *Application) (bool, error) {
owner, _ := util.GetOwnerAndNameFromId(userId)
if owner == "built-in" {
return true, nil
}
@ -346,32 +361,60 @@ func CheckAccessPermission(userId string, application *Application) (bool, error
return false, err
}
allowed := true
allowPermissionCount := 0
denyPermissionCount := 0
allowCount := 0
denyCount := 0
for _, permission := range permissions {
if !permission.IsEnabled {
if !permission.IsEnabled || permission.ResourceType != "Application" || !permission.isResourceHit(application.Name) {
continue
}
isHit := false
for _, resource := range permission.Resources {
if application.Name == resource {
isHit = true
break
if !permission.isUserHit(userId) && !permission.isRoleHit(userId) {
if permission.Effect == "Allow" {
allowPermissionCount += 1
} else {
denyPermissionCount += 1
}
continue
}
if isHit {
containsAsterisk := ContainsAsterisk(userId, permission.Users)
if containsAsterisk {
return true, err
enforcer, err := getPermissionEnforcer(permission)
if err != nil {
return false, err
}
var isAllowed bool
isAllowed, err = enforcer.Enforce(userId, application.Name, "Read")
if err != nil {
return false, err
}
if isAllowed {
if permission.Effect == "Allow" {
allowCount += 1
}
enforcer := getPermissionEnforcer(permission)
if allowed, err = enforcer.Enforce(userId, application.Name, "read"); allowed {
return allowed, err
} else {
if permission.Effect == "Deny" {
denyCount += 1
}
}
}
return allowed, err
// Deny-override, if one deny is found, then deny
if denyCount > 0 {
return false, nil
} else if allowCount > 0 {
return true, nil
}
// For no-allow and no-deny condition
// If only allow permissions exist, we suppose it's Deny-by-default, aka no-allow means deny
// Otherwise, it's Allow-by-default, aka no-deny means allow
if allowPermissionCount > 0 && denyPermissionCount == 0 {
return false, nil
}
return true, nil
}
func CheckUsername(username string, lang string) string {
@ -391,10 +434,6 @@ func CheckUsername(username string, lang string) string {
}
func CheckUpdateUser(oldUser, user *User, lang string) string {
if user.DisplayName == "" {
return i18n.Translate(lang, "user:Display name cannot be empty")
}
if oldUser.Name != user.Name {
if msg := CheckUsername(user.Name, lang); msg != "" {
return msg
@ -404,7 +443,7 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
}
}
if oldUser.Email != user.Email {
if HasUserByField(user.Name, "email", user.Email) {
if HasUserByField(user.Owner, "email", user.Email) {
return i18n.Translate(lang, "check:Email already exists")
}
}

View File

@ -42,7 +42,7 @@ func resetUserSigninErrorTimes(user *User) {
return
}
user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
}
func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
@ -61,7 +61,7 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
}
// update user
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, user.IsGlobalAdmin)
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
if leftChances == 0 && enableCaptcha {
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))

View File

@ -19,6 +19,7 @@ package object
import (
"crypto/tls"
"github.com/casdoor/casdoor/email"
"github.com/casdoor/gomail/v2"
)
@ -35,9 +36,7 @@ func getDialer(provider *Provider) *gomail.Dialer {
}
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
dialer := getDialer(provider)
message := gomail.NewMessage()
emailProvider := email.GetEmailProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, provider.Port, provider.DisableSsl)
fromAddress := provider.ClientId2
if fromAddress == "" {
@ -49,14 +48,7 @@ func SendEmail(provider *Provider, title string, content string, dest string, se
fromName = sender
}
message.SetAddressHeader("From", fromAddress, fromName)
message.SetHeader("To", dest)
message.SetHeader("Subject", title)
message.SetBody("text/html", content)
message.SkipUsernameCheck = true
return dialer.DialAndSend(message)
return emailProvider.Send(fromAddress, fromName, dest, title, content)
}
// DailSmtpServer Dail Smtp server

View File

@ -19,6 +19,7 @@ import (
"github.com/casbin/casbin/v2"
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
"github.com/xorm-io/core"
)
@ -30,10 +31,10 @@ type Enforcer struct {
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Description string `xorm:"varchar(100)" json:"description"`
Model string `xorm:"varchar(100)" json:"model"`
Adapter string `xorm:"varchar(100)" json:"adapter"`
IsEnabled bool `json:"isEnabled"`
Model string `xorm:"varchar(100)" json:"model"`
Adapter string `xorm:"varchar(100)" json:"adapter"`
ModelCfg map[string]string `xorm:"-" json:"modelCfg"`
*casbin.Enforcer
}
@ -120,8 +121,8 @@ func DeleteEnforcer(enforcer *Enforcer) (bool, error) {
return affected != 0, nil
}
func (p *Enforcer) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
func (enforcer *Enforcer) GetId() string {
return fmt.Sprintf("%s/%s", enforcer.Owner, enforcer.Name)
}
func (enforcer *Enforcer) InitEnforcer() error {
@ -154,7 +155,7 @@ func (enforcer *Enforcer) InitEnforcer() error {
if err != nil {
return err
}
err = a.initAdapter()
err = a.InitAdapter()
if err != nil {
return err
}
@ -182,3 +183,80 @@ func GetInitializedEnforcer(enforcerId string) (*Enforcer, error) {
}
return enforcer, nil
}
func GetPolicies(id string) ([]*xormadapter.CasbinRule, error) {
enforcer, err := GetInitializedEnforcer(id)
if err != nil {
return nil, err
}
pRules := enforcer.GetPolicy()
res := util.MatrixToCasbinRules("p", pRules)
if enforcer.GetModel()["g"] != nil {
gRules := enforcer.GetGroupingPolicy()
res2 := util.MatrixToCasbinRules("g", gRules)
res = append(res, res2...)
}
return res, nil
}
func UpdatePolicy(id string, ptype string, oldPolicy []string, newPolicy []string) (bool, error) {
enforcer, err := GetInitializedEnforcer(id)
if err != nil {
return false, err
}
if ptype == "p" {
return enforcer.UpdatePolicy(oldPolicy, newPolicy)
} else {
return enforcer.UpdateGroupingPolicy(oldPolicy, newPolicy)
}
}
func AddPolicy(id string, ptype string, policy []string) (bool, error) {
enforcer, err := GetInitializedEnforcer(id)
if err != nil {
return false, err
}
if ptype == "p" {
return enforcer.AddPolicy(policy)
} else {
return enforcer.AddGroupingPolicy(policy)
}
}
func RemovePolicy(id string, ptype string, policy []string) (bool, error) {
enforcer, err := GetInitializedEnforcer(id)
if err != nil {
return false, err
}
if ptype == "p" {
return enforcer.RemovePolicy(policy)
} else {
return enforcer.RemoveGroupingPolicy(policy)
}
}
func (enforcer *Enforcer) LoadModelCfg() error {
if enforcer.ModelCfg != nil {
return nil
}
model, err := GetModelEx(enforcer.Model)
if err != nil {
return err
} else if model == nil {
return fmt.Errorf("the model: %s for enforcer: %s is not found", enforcer.Model, enforcer.GetId())
}
enforcer.ModelCfg, err = getModelCfg(model)
if err != nil {
return err
}
return nil
}

140
object/get-dashboard.go Normal file
View File

@ -0,0 +1,140 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"sync"
"time"
)
type Dashboard struct {
OrganizationCounts []int `json:"organizationCounts"`
UserCounts []int `json:"userCounts"`
ProviderCounts []int `json:"providerCounts"`
ApplicationCounts []int `json:"applicationCounts"`
SubscriptionCounts []int `json:"subscriptionCounts"`
}
func GetDashboard(owner string) (*Dashboard, error) {
dashboard := &Dashboard{
OrganizationCounts: make([]int, 31),
UserCounts: make([]int, 31),
ProviderCounts: make([]int, 31),
ApplicationCounts: make([]int, 31),
SubscriptionCounts: make([]int, 31),
}
var wg sync.WaitGroup
organizations := []Organization{}
users := []User{}
providers := []Provider{}
applications := []Application{}
subscriptions := []Subscription{}
wg.Add(5)
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&users, &User{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&providers, &Provider{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&applications, &Application{Owner: owner}); err != nil {
panic(err)
}
}()
go func() {
defer wg.Done()
if err := ormer.Engine.Find(&subscriptions, &Subscription{Owner: owner}); err != nil {
panic(err)
}
}()
wg.Wait()
nowTime := time.Now()
for i := 30; i >= 0; i-- {
cutTime := nowTime.AddDate(0, 0, -i)
dashboard.OrganizationCounts[30-i] = countCreatedBefore(organizations, cutTime)
dashboard.UserCounts[30-i] = countCreatedBefore(users, cutTime)
dashboard.ProviderCounts[30-i] = countCreatedBefore(providers, cutTime)
dashboard.ApplicationCounts[30-i] = countCreatedBefore(applications, cutTime)
dashboard.SubscriptionCounts[30-i] = countCreatedBefore(subscriptions, cutTime)
}
return dashboard, nil
}
func countCreatedBefore(objects interface{}, before time.Time) int {
count := 0
switch obj := objects.(type) {
case []Organization:
for _, o := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", o.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []User:
for _, u := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", u.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Provider:
for _, p := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", p.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Application:
for _, a := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", a.CreatedTime)
if createdTime.Before(before) {
count++
}
}
case []Subscription:
for _, s := range obj {
createdTime, _ := time.Parse("2006-01-02T15:04:05-07:00", s.CreatedTime)
if createdTime.Before(before) {
count++
}
}
}
return count
}

View File

@ -226,7 +226,7 @@ func GetGroupUserCount(groupId string, field, value string) (int64, error) {
} else {
return ormer.Engine.Table("user").
Where("owner = ?", owner).In("name", names).
And(fmt.Sprintf("user.%s LIKE ?", util.CamelToSnakeCase(field)), "%"+value+"%").
And(fmt.Sprintf("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%").
Count()
}
}
@ -247,7 +247,7 @@ 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("user.%s like ?", util.CamelToSnakeCase(field)), "%"+value+"%")
}
if sortField == "" || sortOrder == "" {

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