Compare commits

...

1487 Commits

Author SHA1 Message Date
9bbe5afb7c feat: use only one salt arg in CredManager.IsPasswordCorrect() (#3936) 2025-07-07 17:56:25 +08:00
b42391c6ce feat: move needUpdatePassword to response's Data3 field to avoid refresh token conflict (#3931) 2025-07-05 22:48:44 +08:00
fb035a5353 feat: CredManager.GetHashedPassword() only contains one salt arg now (#3928) 2025-07-05 18:41:37 +08:00
b1f68a60a4 feat: set createDatabase to false in TestDumpToFile() (#3924) 2025-07-03 22:50:23 +08:00
201d704a31 feat: improve TikTok username generation logic (#3923) 2025-07-03 20:53:15 +08:00
bf91ad6c97 feat: add Internet-Only captcha rule (#3919) 2025-07-03 02:39:06 +08:00
3ccc0339c7 feat: improve CheckToEnableCaptcha() logic 2025-07-03 02:32:07 +08:00
1f2b0a3587 feat: add user's MFA items (#3921) 2025-07-02 23:05:07 +08:00
0b3feb0d5f feat: use Input.OTP to input totp code (#3922) 2025-07-02 18:22:59 +08:00
568c0e2c3d feat: show Organization.PasswordOptions in login UI (#3913) 2025-06-28 22:13:00 +08:00
f4ad2b4034 feat: remove "@" from name's forbidden chars 2025-06-27 18:41:50 +08:00
c9f8727890 feat: fix bug in InitCleanupTokens() (#3910) 2025-06-27 02:08:18 +08:00
e2e3c1fbb8 feat: support Product.SuccessUrl (#3908) 2025-06-26 22:52:07 +08:00
73915ac0a0 feat: fix issue that LDAP user address was not syncing (#3905) 2025-06-26 09:38:16 +08:00
bf9d55ff40 feat: add InitCleanupTokens() (#3903) 2025-06-26 09:31:59 +08:00
b36fb50239 feat: fix check bug to allow logged-in users to buy product (#3897) 2025-06-25 10:49:20 +08:00
4307baa759 feat: fix Tumblr OAuth's wrong scope (#3898) 2025-06-25 09:55:02 +08:00
3964bae1df feat: fix org's LDAP table wrong link (#3900) 2025-06-25 09:51:40 +08:00
d9b97d70be feat: change CRLF to LF for some files 2025-06-24 09:55:00 +08:00
ca224fdd4c feat: add group xlsx upload button (#3885) 2025-06-17 23:43:38 +08:00
37daea2bbc feat: improve error message in ApplicationEditPage (#3886) 2025-06-17 20:06:52 +08:00
af231bf946 feat: add FieldValidationFilter to check object names (#3877) 2025-06-17 16:11:35 +08:00
6dc7b4d533 feat: get-user API respects org's account item's view rules now (#3882) 2025-06-16 20:09:21 +08:00
12cc0f429e feat: remove support for Non trace verification for Alibaba cloud captcha verification (#3881) 2025-06-13 00:36:29 +08:00
8cc22dec91 feat: upgrade Alibaba cloud captcha provider from v1 to v2 (#3879) 2025-06-12 23:02:36 +08:00
0c08ae5365 feat: Add support for email verification logic (#3875) 2025-06-11 19:17:16 +08:00
c3485268d3 feat: fix "Display name cannot be empty" in /update-user API 2025-06-11 00:32:05 +08:00
64a4956c42 feat: improve getMemoryUsage() 2025-06-09 20:08:55 +08:00
855bdf47e8 feat: fix memory usage in sysinfo page (#3870) 2025-06-09 00:31:34 +08:00
de7e322fbb feat: limit the width of the organization.hasPrivilegeConsent popconfirm (#3869) 2025-06-07 00:30:41 +08:00
4cb0cd7c5a feat: add Organization.HasPrivilegeConsent to block add-user API for the "built-in" org (#3864) 2025-06-06 23:05:01 +08:00
c6a50349cc feat: add missing backend i18n texts (#3863) 2025-06-06 00:03:04 +08:00
8a098a4b6e feat: skip node_modules folder when generating i18n (#3862) 2025-06-05 20:51:14 +08:00
09f98fd24a feat: rollback to Beego 1.12.12 to fix Redis session issue 2025-06-04 22:40:54 +08:00
515d209063 feat: update go dependencies (#3855) 2025-06-03 16:38:06 +08:00
4e17dae2c2 feat: fix unable to remove user from group bug (#3847) 2025-05-28 22:29:40 +08:00
0ad4d82d9c feat: fix GetGroups() API bug when parentGroup is in next page (#3843) 2025-05-28 18:31:52 +08:00
731daf5204 feat: allow org admin to change org user's password without old password (#3841) 2025-05-28 01:23:44 +08:00
b6b77da7cf feat: refactor the code in NewSmtpEmailProvider() (#3832) 2025-05-26 20:23:47 +08:00
8b4637aa3a feat: provide a more complete Excel template for uploading users and fix any bugs (#3831) 2025-05-25 21:23:48 +08:00
87506b84e3 feat: support special chars like "+" in username parameter of /api/get-email-and-phone API (#3824) 2025-05-23 17:29:00 +08:00
fed9332246 feat: can configure Domain field in Nextcloud OAuth provider (#3813) 2025-05-23 17:23:34 +08:00
33afc52a0b feat: can redirect user to login page after linking provider in prompt page (#3820) 2025-05-23 07:15:53 +08:00
9035ca365a feat: improve Indonesia i18n translations (#3817) 2025-05-22 20:42:47 +08:00
b97ae72179 feat: use the standard user struct for JWT-Standard to get a correct userinfo (#3809) 2025-05-21 18:54:42 +08:00
9190db1099 feat: fix bug that token endpoint doesn't return 400/401 when type is object.TokenError (#3808) 2025-05-20 10:39:55 +08:00
1173f75794 feat: return HTTP status 400 instead of 200 in GetOAuthToken() (#3807) 2025-05-20 01:05:43 +08:00
086859d1ce feat: change User.Avatar length back to 500 2025-05-18 09:47:56 +08:00
9afaf5d695 feat: increase User.Avatar length to 1000 2025-05-17 19:59:17 +08:00
521f90a603 feat: fix access_token endpoint cannot read clientId in form when using device code flow (#3800) 2025-05-17 18:53:38 +08:00
4260efcfd0 feat: add useIdAsName field for WeCom OAuth provider (#3797) 2025-05-17 02:27:06 +08:00
d772b0b7a8 feat: fix bug that username will be random with useEmailAsUsername enabled (#3793) 2025-05-16 18:40:50 +08:00
702b390da1 feat: fix MFA preference doesn't work bug (#3790) 2025-05-15 21:04:36 +08:00
b15b3b9335 feat: support adapter in app.conf logConfig (#3784) 2025-05-14 08:27:11 +08:00
f8f864c5b9 feat: add logged-in IDP provider info to access token (#3776) 2025-05-11 09:51:51 +08:00
90e790f83c feat: increase Application.SamlReplyUrl from 100 chars to 500 2025-05-10 22:42:40 +08:00
58413246f3 feat: fix bug that db not found error in createDatabaseForPostgres (#3765) 2025-05-05 18:25:58 +08:00
8f307dd907 feat: upgrade go-teams-notify to v2.13.0 2025-05-05 01:02:27 +08:00
fe42b5e0ba feat: improve checkGroupName() (#3759) 2025-05-03 22:47:42 +08:00
383bf44391 feat: support OIDC device flow: "/api/device-auth" (#3757) 2025-04-30 23:42:26 +08:00
36f5de3203 feat: allow jwks to include the certs from non-admin owner (#3749) 2025-04-28 09:31:56 +08:00
eae69c41d7 feat: add object field filter for webhook (#3746) 2025-04-26 22:05:36 +08:00
91057f54f3 feat: add Pbkdf2DjangoCredManager (#3745) 2025-04-25 16:16:50 +08:00
daa7b79915 feat: improve error handling of webauthn login (#3744) 2025-04-24 01:11:24 +08:00
d3a5539dae feat: fix loading status not reset issue when failed to login (#3743) 2025-04-24 00:57:52 +08:00
7d1c614452 feat: use random name as name if user's name is invalid when created by third party provider (#3742) 2025-04-23 21:30:19 +08:00
e2eafa909b feat: fix MODEL_URL in FaceRecognitionModal 2025-04-21 09:10:30 +08:00
56bcef0592 feat: support application.formCss in forget-password page (#3733) 2025-04-19 22:59:21 +08:00
0860cbf343 feat: can specify content type and http body field mapping for Custom HTTP Email provider (#3730) 2025-04-17 01:59:11 +08:00
2f4180b1b6 feat: add missing currencies in plan edit page (#3727) 2025-04-15 16:01:14 +08:00
e3d5619b25 feat: support custom HTTP headers in custom HttpEmailProvider and hide unused fields (#3723) 2025-04-13 23:52:04 +08:00
019fd87b92 feat: fix code comment typos (#3724) 2025-04-13 17:57:37 +08:00
5c41c6c4a5 feat: add BRL currency 2025-04-11 22:24:45 +08:00
b7fafcc62b feat: improve InitFromFile() code order to fix GetOrganizationApplicationCount always returns 0 bug (#3720) 2025-04-11 01:43:54 +08:00
493ceddcd9 feat: improve error handling in system info page 2025-04-11 01:41:27 +08:00
fc618b9bd5 feat: add validation for optional fields in IntrospectionToken for custom token types (#3717) 2025-04-09 22:27:19 +08:00
a00900e405 feat: fix sqlite bug for failed to lookup Client-side Discoverable Credential: user not exist (#3719) 2025-04-09 22:26:47 +08:00
77ef5828dd feat(introspection): return correct active status for expired or revoked tokens (#3716) 2025-04-09 02:00:30 +08:00
c11f013e04 feat: return "Active: false" for expired token in IntrospectToken() (#3714) 2025-04-08 23:20:44 +08:00
b3bafe8402 feat: fix bug that unable to query webauthnCredentials when db is mssql or postgres in GetUserByWebauthID() (#3712) 2025-04-08 17:51:32 +08:00
f04a431d85 feat: Casdoor's LDAP client supports LDAP server's self-signed certificates now (#3709) 2025-04-07 02:02:32 +08:00
952538916d feat: check application existence in object.AddUser() (#3686) 2025-04-05 16:38:20 +08:00
18bb445e71 feat: update github.com/golang-jwt/jwt dependency to v5 (#3708) 2025-04-05 02:05:41 +08:00
cca88e2cb0 feat: fix bug that when email/sms mfa is not preferred, message will send to masked address (#3705) 2025-04-04 01:08:29 +08:00
86c10fe0ab feat: change org.CountryCodes to mediumtext 2025-04-02 20:23:04 +08:00
c1b3bf0f45 feat: set button to loading status immediately after click (#3696) 2025-04-02 01:15:36 +08:00
62bda61af5 feat: can use provider_hint arg to do OAuth redirect automatically (#3698) 2025-04-02 01:15:20 +08:00
b6f943e326 feat: support WebAuthn login without username and upgrade Go to 1.21 (#3695) 2025-04-01 16:35:59 +08:00
2cc5e82d91 feat: support login button loading state (#3694) 2025-04-01 00:57:24 +08:00
e55cd94298 feat: fix issue that user email is still unverified after signup (#3685) 2025-03-29 21:24:01 +08:00
08f7a05e61 feat: fix MFA + LDAP bug in /check-user-password API (#3681) 2025-03-26 22:11:58 +08:00
4bee21f4a3 feat: use StaticBaseUrl in frontend 2025-03-26 21:32:31 +08:00
5417a90223 feat: fix bug that there is already an object named 'casbin_api_rule' in the database (#3680) 2025-03-25 22:24:58 +08:00
131820e34e feat: add application.ForcedRedirectOrigin 2025-03-24 13:42:35 +08:00
2fcbf7cf6c feat: fix apps page grid style (#3679) 2025-03-22 18:19:14 +08:00
14ade8b7e4 feat: fix provider test API's missing owner and name args for auth (#3676) 2025-03-22 17:53:20 +08:00
a11fe59704 feat: support widget items config in org (#3674) 2025-03-21 23:00:07 +08:00
af55d0547f feat: improve frontend i18n strings 2025-03-21 21:03:29 +08:00
81102f8298 feat: fix permission update bug when both org and model are modified (#3671) 2025-03-20 09:05:27 +08:00
141372cb86 feat: support face ID provider (#3666) 2025-03-19 22:57:35 +08:00
15a037ca74 feat: increase frontend build memory to 4096 in Dockerfile (#3672)
297.8 FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
2025-03-19 10:40:34 +08:00
73c680d56f feat: avoid using body in GET requests for AirwallexClient payment provider (#3669) 2025-03-18 20:04:15 +08:00
aafc16e4f4 feat: fix dynamic width of navbar UI (#3664) 2025-03-16 16:12:58 +08:00
7be026dd1f feat: Support for selecting existing users or scanning a QR code when logging into Dingtalk (#3660) 2025-03-13 21:49:07 +08:00
3e7938e5f6 feat: don't panic when provider not found in Login() API (#3659) 2025-03-13 21:35:51 +08:00
30789138e2 feat: fix faceId loop error caused by async (#3651) 2025-03-11 21:03:04 +08:00
9610ce5b8c feat: can add faceId by uploading images (#3641) 2025-03-09 01:29:25 +08:00
a39a311d2f feat: fix webhook bug in RecordEx JSON (#3642) 2025-03-08 00:20:59 +08:00
08e41ab762 feat: can specify user fields in webhook edit page (#3635) 2025-03-04 14:16:16 +08:00
85ca318e2f feat: can assign default group during signup (#3633) 2025-03-02 22:55:51 +08:00
9032865e60 feat: support mobile background for login page (#3629) 2025-03-01 23:01:15 +08:00
5692522ee0 feat: update user language when the language changed on login page (#3628) 2025-03-01 22:28:20 +08:00
cb1882e589 feat: fix MFA bug, revert PR: "feat: don't send verification code if failed signin limit is reached" (#3627) 2025-03-01 12:58:28 +08:00
41d9422687 feat: increase username limit to 255 chars 2025-03-01 00:44:34 +08:00
3297db688b feat: support shared cert in GetCert() API 2025-02-28 23:02:13 +08:00
cc82d292f0 feat: set frontend origin to 7001 if in dev mode (#3615) 2025-02-26 22:35:50 +08:00
f2e3037bc5 feat: don't send verification code if failed signin limit is reached (#3616) 2025-02-26 22:34:14 +08:00
d986a4a9e0 feat: fix bug that initialize group children as empty array instead of empty string (#3620) 2025-02-26 08:50:09 +08:00
2df3878c15 feat: fix bug that group.HaveChildren is never set to false bug Something isn't working (#3609) 2025-02-22 01:46:35 +08:00
24ab8880cc feat: fix bug that organization might be nil in some case and cause nil point error (#3608) 2025-02-21 23:43:30 +08:00
f26b4853c5 feat: bump Go version to go 1.18 (#3599) 2025-02-21 13:10:17 +08:00
d78e8e9776 feat: fix LDAP filter condition will return nil if error happened (#3604) 2025-02-21 13:09:39 +08:00
d61f9a1856 feat: update antd from 5.2.3 to 5.24.1 (#3593) 2025-02-18 20:54:10 +08:00
aa52af02b3 feat: fix style props of Editor (#3590) 2025-02-17 13:39:49 +08:00
2a5722e45b feat: add detail sidebar for record list page, improve token list page (#3589) 2025-02-16 22:01:25 +08:00
26718bc4a1 feat: update signinUrl storage to include pathname and query parameters only to prevent new tab popup after password reset (#3587) 2025-02-14 20:31:36 +08:00
f8d44e2dca feat: set default CountryCode for user 2025-02-14 16:54:25 +08:00
26eea501be feat: don't use organization.MasterVerificationCode when sending 2025-02-14 16:54:25 +08:00
63b8e857bc feat: update signinUrl storage to include path and query parameters in forced reset password flow (#3583) 2025-02-14 01:32:10 +08:00
81b336b37a feat: replace react-codemirror2 with @uiw/react-codemirror (#3577)
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2025-02-14 00:10:33 +08:00
9c39179849 feat: fix bug that user forbidden check will be skipped in OAuth login (#3580) 2025-02-13 13:14:44 +08:00
37d93a5eea feat: update SendgridEmailProvider to support dynamic host/path, add From name field (#3576)
* feat: add fields into UI FromName, Host, Endpoint

* feat: update SendgridEmailProvider support dynamic host/path client init, code convention
2025-02-13 00:51:31 +08:00
e926a07c58 feat: add "User type" to user list page 2025-02-12 21:29:18 +08:00
9c46344e68 feat: improve default org passwordOptions handling 2025-02-12 21:20:32 +08:00
c0ec73dfd3 feat: fix tableNamePrefix doesn't work bug in /get-dashboard API (#3572) 2025-02-11 17:20:45 +08:00
b1b6ebe692 feat(jwt): add azp claim to ID token (#3570)
Added the `azp` (Authorized Party) claim to various JWT token structures
including Claims, ClaimsShort, ClaimsWithoutThirdIdp, and ClaimsStandard.
Updated the generateJwtToken and getClaimsCustom functions to handle the
new claim. This change aligns with the OpenID Connect specification.
2025-02-10 20:44:44 +08:00
a0931e4597 feat: add userTypes to Organization 2025-02-09 17:12:13 +08:00
c181006661 feat: cache theme in signup page (#3568) 2025-02-09 15:12:35 +08:00
2e83e49492 feat: fix bug due to null characters in descriptor when creating a payment intent (#3567) 2025-02-08 19:35:51 +08:00
5661942175 feat: add CLI version cache and proxy support (#3565)
* feat: add CLI version cache mechanism

* feat: add /api/refresh-engines to allowed endpoints in demo mode

* feat: add proxy support for cli downloader

* feat: add SafeGoroutine for CLIDownloader initialization

* refactor: optimize code structure
2025-02-08 19:34:19 +08:00
7f9f7c6468 feat: add CLI tools auto-downloader and updater (#3559)
* feat: add CLI downloader feature

* feat: add CLI refresh endpoint and scheduler

* feat: improve binary names mapping for different platforms and architectures

* fix: format binary names in getBinaryNames function

* fix: change file permission notation to octal in cli_downloader.go

* feat: add isDemoMode check for CLI downloader features
2025-02-07 19:22:56 +08:00
b7a818e2d3 feat: support AirWallex payment provider (#3558)
* feat: support AirWallex payment provider

* chore: add some information due to AirWallex's risk control policy
2025-02-07 19:19:30 +08:00
1a8cfe4ee6 feat: can fetch SAML metadata from URL (#3560) 2025-02-06 23:50:39 +08:00
b3526de675 feat: add checkOrgMasterVerificationCode() 2025-02-06 23:46:22 +08:00
3b9e08b70d feat: Fix reset password flow for shared application (#3556) 2025-02-06 18:03:23 +08:00
cfc6015aca feat: rename Casdoor app URL to authenticator (#3553) 2025-02-05 23:08:06 +08:00
1600a6799a feat: return error for updateUsedByCode() 2025-02-05 13:40:41 +08:00
ca60cc3a33 feat: show SAML cert parse error better in frontend (#3551) 2025-02-05 10:06:02 +08:00
df295717f0 feat: can define what Casdoor pages an org admin can see via Organization.NavItems (#3539)
* feat: support define what Casdoor pages an org admin can see

* feat: remove useless code

* fix: fix NavItemNodes i18next invalid

* fix: only global admin can edit navItems

* fix: move navItem tree to extra file
2025-02-03 00:40:21 +08:00
e3001671a2 feat: fix bug that can not delete user if user doesn't belong to any group (#3544) 2025-02-02 17:54:05 +08:00
bbe2162e27 feat: fix bug in GetTokenByTokenValue() (#3541) 2025-01-30 00:48:20 +08:00
92b5ce3722 feat: add identifier validation for security in RunCasbinCommand (#3535)
* feat: add identifier validation for security in RunCasbinCommand

* refactor: update identifier validation to use SHA-256 hash and timestamp
2025-01-29 18:30:06 +08:00
bad21fb6bb feat: check empty password in CheckPassword() 2025-01-28 21:13:59 +08:00
5a78dcf06d feat: fix Casbin Permissions Not Working When Auto-login is Enabled (#3537)
* fix: fix Casbin Permissions Not Working When Auto-login is Enabled

* fix: fix oauth fastLogin not support permission
2025-01-28 19:15:53 +08:00
558b168477 feat: can verify OTP during OAuth login (#3531)
* feat: support verify OTP during OAuth login

* fix: fail to login if mfa not enable

* fix: fail to login if mfa not enable

* fix: fix mfaRequired not valid in saml/auth
2025-01-27 19:37:26 +08:00
802b6812a9 feat: fix strange "Email is invalid" error in forget password page (#3527) 2025-01-23 14:35:11 +08:00
a5a627f92e feat: optimize get-groups API and GroupListPage (#3518)
* fix: optimize get-groups api and GroupListPage

* fix: fix linter issue
2025-01-23 09:47:39 +08:00
9701818a6e feat: delete groups for user while deleting user (#3525) 2025-01-23 09:46:33 +08:00
06986fbd41 feat: fix theme filter for other URLs like SAML (#3523)
* fix: fix error cause by theme filter

* fix: add saml url to theme filter and use getGetOwnerAndNameFromIdWithError instead of using GetOwnerAndNameFromId

* fix: fix code error

* fix: add support for cas and pack judgement into a function

* fix: fix linter err
2025-01-22 19:12:12 +08:00
3d12ac8dc2 feat: improve HandleScim() 2025-01-22 16:15:19 +08:00
f01839123f feat: fix missing param recoveryCodes in /mfa/setup/enable API (#3520) 2025-01-21 22:56:02 +08:00
e1b3b0ac6a feat: allow user use other mfaType in mfa step and skip redundant MFA verification (#3499)
* feat: allow user use other mfaType in mfa step and skip redundant MFA verification

* feat: improve format
2025-01-21 20:16:18 +08:00
4b0a2fdbfc feat: append HTML document title and favicon to cookie (#3519)
* feat: append HTML document title and favicon to cookie

* feat: remove useless cookie
2025-01-21 19:42:21 +08:00
db551eb24a feat: LDAP user can reset password with old password and new password (#3516)
* feat: support user reset password with old password and new password

* feat: merge similar code
2025-01-20 21:42:05 +08:00
18b49bb731 feat: can reset LDAP password with different password encryption methods (#3513) 2025-01-20 20:00:23 +08:00
17653888a3 feat: refactor the TestSmtpServer code 2025-01-20 03:17:09 +08:00
ee16616df4 feat: support socks5Proxy for AWS Email provider 2025-01-20 02:39:23 +08:00
ea450005e0 feat: fix "logo" bug in footer 2025-01-20 00:01:46 +08:00
4c5ad14f6b fix: spin will squeeze login panel (#3509) 2025-01-19 23:35:04 +08:00
49dda2aea5 feat: append footerHtml to cookie (#3508) 2025-01-19 23:34:43 +08:00
a74a004540 feat: append logo url to cookie (#3507) 2025-01-19 08:02:44 +08:00
2b89f6b37b feat: fix issue that application theme is ignored in appendThemeCookie() (#3506) 2025-01-18 21:28:39 +08:00
c699e35e6b feat: load theme from first HTML render cookie (#3505) 2025-01-18 19:04:16 +08:00
e28d90d0aa feat: support CUCloud SMN notification provider (#3502) 2025-01-17 08:35:31 +08:00
4fc7600865 feat: skip update user ranking if ranking not in accountItem (#3500) 2025-01-14 22:43:49 +08:00
19f62a461b feat: fix SAML's redirectUrl and POST ProtocolBinding (#3498) 2025-01-13 20:55:37 +08:00
7ddc2778c0 feat: show error message when organization doesn't have default application in invitation edit page (#3495)
* fix: inform user when organization haven't default application in signup page

* fix: include org name in the error message
2025-01-12 22:48:21 +08:00
b96fa2a995 feat: skip GetUserCount() if there is no quota limit (#3491) 2025-01-10 22:28:25 +08:00
fcfb73af6e feat: increase org password field length to 200 2025-01-09 20:07:49 +08:00
43bebc03b9 feat: fix crash in roleChangeTrigger() 2025-01-09 16:41:56 +08:00
c5f25cbc7d feat: getPidByPort() supports alpine now (#3483)
Signed-off-by: WindSpiritSR <simon343riley@gmail.com>
2025-01-08 12:18:46 +08:00
3feb6ce84d feat: add Kwai OAuth provider (#3480)
* feat: add Kwai OAuth provider

* fix: incorrect parameter in getAuthUrl
2025-01-08 00:09:16 +08:00
08d6b45fc5 feat: keeps "build" folder during yarn build 2025-01-07 23:38:50 +08:00
56d0de64dc feat: support StopOldInstance() 2025-01-07 21:39:21 +08:00
1813e8e8c7 feat: return goroutine error in get-dashboard API (#3479) 2025-01-07 10:35:45 +08:00
e27c764a55 feat: fix bug that GitHub oauth provider shows error if failed to fetch user's email (#3474)
* fix: fix github idp will stop login if it cannot fetch user's email through al restful api

* Update github.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2025-01-05 20:25:42 +08:00
e5a2057382 feat: fix empty scope bug in RefreshToken API (#3467)
* fix: fix scope will be empty when user not passing scope in refresh api

* fix: promote code format
2025-01-02 12:53:17 +08:00
8457ff7433 feat: support radiusDefaultOrganization in app.conf 2025-01-02 00:10:58 +08:00
888a6f2feb feat: add regex to restrict Email addresses in OAuth provider (#3465)
* feat: support use regex expression to limit email receiver address

* feat: limit in correct pos

* feat: promote code format

* feat: promote code format

* fix: fix linter issue
2025-01-02 00:00:57 +08:00
b57b64fc36 feat: add origin field for mfaAccountTable (#3463) 2024-12-29 22:51:21 +08:00
0d239ba1cf feat: improve the error message of GitHub OAuth provider (#3462) 2024-12-29 21:54:54 +08:00
8927e08217 feat: speed up GetDashboard() by only fetching last 30 days data (#3458)
* feat: only check 30 days data

* refactor: refactor GetDashboard to reduce code line

* refactor: refactor GetDashboard to reduce code line

* refactor: remove unused where

* fix: fix error code
2024-12-29 16:15:52 +08:00
0636069584 feat: only fetch created_time field to reduce data size in get-dashboard API (#3457) 2024-12-28 23:52:19 +08:00
4d0f73c84e feat: fix Casdoor OAuth provider doesn't use domain field bug 2024-12-28 10:01:56 +08:00
74a2478e10 feat: Make MinIO storage provider region setting configurable (#3433)
* fix: Make MinIO provider region setting configurable

* Fix: Correct the issue where modifications to MinIO's default logic caused behavioral discrepancies
2024-12-23 16:07:14 +08:00
acc6f3e887 feat: escape the avatal URL in CAS response (#3434) 2024-12-20 17:11:58 +08:00
185ab9750a feat: fix VerificationRecord.IsUsed JSON Field Mapping 2024-12-18 13:56:54 +08:00
48adc050d6 feat: can pass empty user id on user update (#3443) 2024-12-18 07:56:44 +08:00
b0e318c9db feat: add localized tab titles for Basic and Advanced Editors (#3431)
* feat: add localized tab titles for Basic and Advanced Editors

* docs: update translations for model editor labels in multiple locales
2024-12-16 08:34:13 +08:00
f9a6efc00f feat: advanced model editor should support changing UI language (#3430) 2024-12-15 15:53:29 +08:00
bd4a6775dd feat: get github user email with user/emails api (#3428)
* feat: get user email use `user/emails` api

* feat: improve code format

* feat: improve code format
2024-12-15 10:28:18 +08:00
e3a43d0062 feat: improve the advanced editor of model edit page (#3427) 2024-12-15 02:07:02 +08:00
0cf281cac0 feat: fix record's password regex bug (#3421) 2024-12-11 08:43:03 +08:00
7322f67ae0 feat: add model, adapter and enforcer to the dashboard page chart (#3413)
* [feature] Add more data (Model, Adapter, Enforcer) to the dashboard page chart #3379

* feat: add model, adapter, enforcer to dashboard
2024-12-09 16:07:39 +08:00
b927c6d7b4 feat: support LDAP's SetPassword (#3395)
* fix: Resolve the issue mentioned in #3392

* fix: Change checkLdapUserPassword to CheckLdapUserPassword.

* fix: the issue mentioned by hsluoyz.

* fix: Check if the user parameter is nil

* fix: use existing i18n message
2024-12-09 16:06:24 +08:00
01212cd1f3 feat: add AiAssistantUrl to frontend config (#3385) 2024-12-08 20:44:28 +08:00
bf55f94d41 feat: support CUCloud OSS storage provider (#3400) 2024-12-08 20:24:38 +08:00
f14711d315 feat: fix frontend bug 2024-12-07 21:53:01 +08:00
58e1c28f7c feat: support LDAPS protocol (#3390)
* feat: support ldaps

* fix: unencrypted port 389 not work after enable SSL
fix: remove useless conf and set ldapsCertId to empty
fix: return and log getTLSconfig error

* fix: remove unused setting

* fix: check nil condition

* fix: not log fail when certId is empty
2024-12-07 21:26:07 +08:00
922b19c64b feat: reduce i18n items 2024-12-07 21:22:57 +08:00
1d21c3fa90 feat: fix issue that introspectionResponse uses Bearer instead of raw tokenType (#3399) 2024-12-05 20:59:30 +08:00
6175fd6764 feat: make token_type_hint optional (#3397) 2024-12-04 20:10:15 +08:00
2ceb54f058 feat: support most popular currencies (#3388) 2024-12-01 21:46:44 +08:00
aaeaa7fefa feat: update go sms sender (#3386) 2024-11-29 23:00:34 +08:00
d522247552 feat: fix countryCode param bug in MFA login (#3384) 2024-11-29 21:46:06 +08:00
79dbdab6c9 feat: fix "dest is missing" bug in MFA login (#3383)
* feat: support stateless mfa setup

* Revert "feat: support stateless mfa setup"

This reverts commit bd843b2ff3.

* feat: use new implement

* fix: missing set field on login
2024-11-29 19:59:30 +08:00
fe40910e3b feat: support stateless MFA setup (#3382) 2024-11-29 19:50:10 +08:00
2d1736f13a feat: Add more data to the dashboard page chart #3365 (#3375)
* test

* feat: #3365 add more dada to the dashboard page chart

* feat: #3365 Add more data to the dashboard page chart
2024-11-26 09:16:35 +08:00
12b4d1c7cd feat: change LDAP attribute from cn to title for correct username mapping (#3378) 2024-11-26 09:13:05 +08:00
a45d2b87c1 feat: Add translations for Persian (#3372) 2024-11-23 16:24:07 +08:00
8484465d09 feat: fix SAML failed to redirect issue when login api returns RequiredMfa (#3364) 2024-11-21 20:31:56 +08:00
dff65eee20 feat: Force users to change their passwords after 3/6/12 months (#3352)
* feat: Force users to change their passwords after 3/6/12 months

* feat: Check if the password has expired by using the last_change_password_time field added to the user table

* feat: Use the created_time field of the user table to aid password expiration checking

* feat: Rename variable
2024-11-19 21:06:52 +08:00
596016456c feat: update CI's upload-artifact and download-artifact actions to v4 (#3361)
v3 of `actions/upload-artifact` and `actions/download-artifact` will be
fully deprecated by 5 December 2024. Jobs that are scheduled to run
during the brownout periods will also fail. See [1][2].

[1]: https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/
[2]: https://github.blog/changelog/2024-11-05-notice-of-breaking-changes-for-github-actions/

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2024-11-19 00:07:59 +08:00
673261c258 feat: fix placeholder bug in signin page (#3359) 2024-11-17 00:14:26 +08:00
3c5985a3c0 fix: fix several bugs in samlRequest (#3358) 2024-11-17 00:14:04 +08:00
4f3d62520a feat: fix the dashboard page shows zero data in mobile phone (#3356) 2024-11-16 22:02:49 +08:00
96f8b3d937 feat: fix SAML metadata URL and XML generation issue when enablePostBinding is enabled (#3354) 2024-11-16 15:35:30 +08:00
7ab5a5ade1 feat: add processArgsToTempFiles() to RunCasbinCommand() 2024-11-15 20:25:48 +08:00
5cbd0a96ca Use json format for argString in RunCasbinCommand() 2024-11-15 18:27:25 +08:00
7ccd8c4d4f feat: add RunCasbinCommand() API 2024-11-15 17:44:57 +08:00
b0fa3fc484 feat: add Casbin CLI API to Casdoor (#3351) 2024-11-15 16:10:22 +08:00
af01c4226a feat: add Organization.PasswordExpireDays field 2024-11-15 11:33:28 +08:00
7a3d85a29a feat: update github token to fix CI cannot release issue (#3348) 2024-11-14 18:05:56 +08:00
fd5ccd8d41 feat: support copying token to clipboard for casdoor-app (#3345)
* feat: support copy token to clipboard for casdoor-app auth

* feat: abstract casdoor-app related code
2024-11-13 17:06:09 +08:00
a439c5195d feat: get token only by hash now, remove get-by-value backward-compatible code 2024-11-13 17:04:27 +08:00
ba2e997d54 feat: fix CheckUpdateUser() logic to fix add-user error 2024-11-06 08:34:13 +08:00
0818de85d1 feat: fix username checks when organization.UseEmailAsUsername is enabled (#3329)
* feat: Username support email format

* feat: Only fulfill the first requirement

* fix: Improve code robustness
2024-11-05 20:38:47 +08:00
457c6098a4 feat: fix MFA empty CountryCode bug and show MFA error better in frontend 2024-11-04 16:17:24 +08:00
60f979fbb5 feat: fix MfaSetupPage empty bug when user's signup application is empty 2024-11-04 00:04:47 +08:00
ff53e44fa6 feat: use virtual select UI in role edit page (#3322) 2024-11-03 20:05:34 +08:00
1832de47db feat: fix bug in CheckEntryIp() 2024-11-03 20:00:52 +08:00
535eb0c465 fix: fix IP Whitelist field bug in application edit page 2024-11-03 19:55:59 +08:00
c190634cf3 feat: show Domain field for Qiniu storage provider (#3318)
allow Qiniu Provider to edit the Domain property in the edit page.
2024-10-27 14:10:58 +08:00
f7559aa040 feat: set created time if not presented in AddUser() API (#3315) 2024-10-24 23:06:05 +08:00
1e0b709c73 feat: pass signin method to CAS login to fix bug (#3313) 2024-10-24 14:56:12 +08:00
c0800b7fb3 feat: add util.IsValidOrigin() to improve CORS filter (#3301)
* fix: CORS check issue

* fix: promote format

* fix: promote format

* fix: promote format

* fix: promote format

* Update application.go

* Update cors_filter.go

* Update validation.go

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-10-20 20:09:21 +08:00
6fcdad2100 feat: fix bug that fails to login when PasswordObfuscator is enabled (#3299) 2024-10-19 23:09:59 +08:00
69d26d5c21 feat: add-user/update-user API should check if username/id/email/phone has duplicated with existing user (#3295) 2024-10-18 22:18:37 +08:00
94e6b5ecb8 feat: fix bug in SetPassword() API (#3296) 2024-10-18 20:50:43 +08:00
95e8bdcd36 feat: add initDataNewOnly to app.conf to skip overriding existing data in initDataFromFile() (#3294)
* feat: support control whether overwrite existing data during initDataFromFile

* feat: change conf var name

* feat: change conf var name
2024-10-18 00:08:08 +08:00
6f1f93725e feat: fix GetAllActions()'s bug (#3289) 2024-10-16 21:55:06 +08:00
7ae067e369 feat: only admin can specify user in BuyProduct() (#3287)
* fix: balance can be used without login

* fix: balance can be used without login

* fix: fix bug

* fix: fix bug
2024-10-16 00:02:04 +08:00
dde936e935 feat: fix null application crash in CheckEntryIp() 2024-10-15 22:11:15 +08:00
fb561a98c8 feat: fix null user crash in RefreshToken() 2024-10-15 21:38:33 +08:00
7cd8f030ee feat: support IP limitation for user entry pages (#3267)
* feat: support IP limitation for user entry pages

* fix: error message, ip whiteList, check_entry_ip

* fix: perform checks on the backend

* fix: change the implementation of checking IpWhitelist

* fix: add entryIpCheck in SetPassword and remove it from VerifyCode

* fix: remove additional error message pop-ups

* fix: add isRestricted and show ip error in EntryPage.js

* fix: error message

* Update auth.go

* Update check_ip.go

* Update check_ip.go

* fix: update return value of the check function from string to error

* fix: remoteAddress position

* fix: IP whitelist

* fix: clientIp

* fix:add util.GetClientIpFromRequest

* fix: remove duplicate IP and port separation codes and remove extra special characters after clientIp

* fix: gofumpt

* fix: getIpInfo and localhost

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-10-15 20:40:14 +08:00
a3f8ded10c feat: refactor util.GetClientIpFromRequest() 2024-10-15 12:22:38 +08:00
e3d135bc6e feat: improve MFA desc text (#3284)
* fix: fix i18n error for mfa

* fix: fix i18n error for mfa

* fix: promote translate
2024-10-14 18:31:48 +08:00
fc864b0de4 feat: support ".login-panel-dark" CSS for signup/login pages (#3269)
* feat: add custom dark mode CSS for login and registration forms.

* refactor: extract dark theme check to Setting.js
2024-10-13 22:31:54 +08:00
3211bcc777 feat: add getCaptchaRule() to fix bug (#3281)
* feat: update captcha rule when the login page component is mounted

* fix: remove enableCaptchaModel from the state of the login page to avoid inconsistency issues

* fix: use this.getApplicationObj() instead of this.props.application
2024-10-12 10:02:45 +08:00
9f4430ed04 feat: fix MFA's i18n error (#3273) 2024-10-08 21:58:06 +08:00
05830b9ff6 feat: update import lib: github.com/casdoor/ldapserver 2024-10-08 19:18:56 +08:00
347b25676f feat: dark mode now works for login/signup pages too (#3252)
* fix: trying to fix dark mode not applying on login/registration interface

* fix: trying to fix dark mode not applying on login/registration interface

* fix: trying to fix dark mode not applying on login/registration interface

* fix: Clean up unused code

* fix: loginBackgroundDark move to App.less

* fix: fix typo
2024-10-05 21:26:25 +08:00
2417ff84e6 feat: support initial group assignment for new invited users via invitation.SignupGroup field (#3266) 2024-10-04 20:15:51 +08:00
468631e654 feat: support "All" in organization's country codes (#3264) 2024-10-03 22:58:09 +08:00
e1dea9f697 feat: add organization's PasswordObfuscator to obfuscate login API's password (#3260)
* feat: add PasswordObfuscator to the login API

* fix: change key error message

* fix: remove unnecessary change

* fix: fix one

* fix: fix two

* fix: fix three

* fix: fix five

* fix: disable organization update when key is invalid

* fix: fix six

* fix: use Form.Item to control key

* fix: update obfuscator.js

* Update obfuscator.go

* Update obfuscator.go

* Update auth.go

* fix: remove real-time key monitoring

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-10-03 10:38:37 +08:00
c0f22bae43 feat: better handling of organization.AccountItems on init_data import (#3263)
* Better handling of accountitems on init_data import.

* Removed commented code.

* Update init_data.go

* Update init_data.go

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-10-03 08:49:09 +08:00
c9635d9e2b feat: improve i18n (#3259) 2024-10-01 00:10:49 +08:00
3bd52172ea feat: add Hide-Password option for signin method rule field (#3258) 2024-09-30 23:31:41 +08:00
bf730050d5 feat: increase Organization.Favicon to 200 chars 2024-09-29 11:45:56 +08:00
5b733b7f15 feat: improve filterRecordIn24Hours() logic 2024-09-29 11:45:15 +08:00
034f28def9 feat: logout if app.conf's inactiveTimeoutMinutes is reached (#3244)
* feat: logout if there's no activities for a long time

* fix: change the implementation of updating LastTime

* fix: add logoutMinites to app.conf

* fix: change the implementation of judgment statement

* fix: use sync.Map to ensure thread safety

* fix: syntax standards and Apache headers

* fix: change the implementation of obtaining logoutMinutes in app.conf

* fix: follow community code standards

* fix: <=0 or empty means no restriction

* Update logout_filter.go

* Update app.conf

* Update main.go

* Update and rename logout_filter.go to timeout_filter.go

* Update app.conf

* Update timeout_filter.go

* fix: update app.conf

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-09-27 01:18:02 +08:00
c86ac8e6ad feat: fix UTF-8 charset for Alipay IdP (#3247) 2024-09-27 00:59:52 +08:00
d647eed22a feat: add OIDC WebFinger support (#3245)
* feat: add WebFinger support

* lint: used gofumpt

* oidc: ensure webfinger rel is checked
2024-09-26 13:06:36 +08:00
717c53f6e5 feat: support enableErrorMask2 config 2024-09-25 19:37:14 +08:00
097adac871 feat: support single-choice and multi-choices in signup page (#3234)
* feat: add custom signup field

* feat: support more field in signup page

* feat: support more field in signup page

* feat: support more field in signup page

* feat: Reduce code duplication in form item rendering

* feat: Simplify gender and info checks using includes

* feat: update translate

* Revert "feat: update translate"

This reverts commit 669334c716.

* feat: address feedback from hsluoyz
2024-09-25 12:48:37 +08:00
74543b9533 feat: improve QR code for casdoor-app (#3226)
* feat: simplify login url for casdoor-app

* feat: add token check

* fix: improve logic
2024-09-23 22:27:58 +08:00
110dc04179 feat: Revert "feat: fix permission problem in standard image" (#3231)
This reverts commit 6464bd10dc.
2024-09-23 22:19:27 +08:00
6464bd10dc feat: fix permission problem in standard image (#3228) 2024-09-23 18:40:39 +08:00
db878a890e feat: add type and options to signup items 2024-09-21 23:40:29 +08:00
12d6d8e6ce feat: fix cookie expire time too short bug 2024-09-21 22:45:13 +08:00
8ed6e4f934 feat: improve UI for "No account?" 2024-09-21 07:35:33 +08:00
ed9732caf9 feat: add condition for getWebBuildFolder function (#3219) 2024-09-20 23:59:13 +08:00
0de4e7da38 feat: fix organization pagination count error (#3215)
* fix(organization): ensure count includes shared organizations

Adjust the `GetOrganizationCount` function to account for shared organizations by adding
an additional parameter and modifying the count query accordingly. This change ensures that
the organization count correctly reflects shared organizations within the system.

* ```fix(organization): optimize GetOrganizationCount query

Refactor the GetOrganizationCount function to use a more efficient search
method by leveraging the 'is_shared' field directly in the query condition.
This change improves the performance for counting organizations by avoiding
unnecessary iteration over potentially large result sets.
```

---------

Co-authored-by: CuiJing <cuijing@tul.com.cn>
2024-09-20 23:58:46 +08:00
a330fbc11f docs: fix Docker link 2024-09-17 20:45:32 +08:00
ed158d4981 feat: support advanced editor in model edit page (#3176)
* feat: integrate external model editor and handle message events for model updates

* feat: add CasbinEditor and IframeEditor components for model editing

* feat: add tabbed editor interface for CasbinEditor

* fix: Synchronize content between basic and advanced editors

* refactor: simplify CasbinEditor and ModelEditPage components

* refactor: Refactor CasbinEditor for improved iframe initialization and model synchronization

* refactor: update default state of CasbinEditor active tab to "advanced

* chore: add Apache License header to CasbinEditor.js and IframeEditor.js files

* refactor: update CasbinEditor class names for consistency
2024-09-16 22:25:25 +08:00
8df965b98d feat: improve SAML XML's xmlns to fix SAML support for some clouds (#3207) 2024-09-16 08:01:28 +08:00
2c3749820e feat: add application.UseEmailAsSamlNameId field for SAML (#3203)
* feat: Add option to use email as SAML NameID based on application config

- Updated NewSamlResponse11 to accept an application parameter.
- Conditionally set SAML NameIdentifier to user's email or username based on application.UseEmailAsNameId.

* refactor: Update GetValidationBySaml to pass application to NewSamlResponse11

- Modified GetValidationBySaml function to include application parameter in NewSamlResponse11 call.

* feat: Rename field and update logic for using Email as SAML NameID

- Renamed the `UseEmailAsNameId` field to `UseEmailAsSamlNameId` in the `Application` struct.
- Updated `NewSamlResponse` and `NewSamlResponse11` functions to use `UseEmailAsSamlNameId` for setting the NameID value.
- Modified `ApplicationEditPage.js` to reflect the field name change and update the corresponding logic.
2024-09-15 23:00:50 +08:00
0b17cb9746 feat: make Organization.EnableSoftDeletion and User.IsDeleted work (#3205)
* feat: make Organization.EnableSoftDeletion and User.IsDeleted work

* fix: add handling of the situation where organization is nil
2024-09-15 14:35:44 +08:00
e2ce9ad625 feat: handle null account item issue in CheckPermissionForUpdateUser() (#3202)
* feat: improve the logic of the permission check code for users to modify account items

* fix: add skip operation for deleted account items in update-user API

* fix: add the function of removing deleted account item
2024-09-14 15:00:10 +08:00
64491abc64 feat: fix CORS issue of /api/acs for SAML IdP (#3200)
* fix: fix CORS problem of /api/acs when login with saml idp

* fix: fix origin get null when receive post with http protocol
2024-09-14 12:48:51 +08:00
934a8947c8 feat: fix CAS logout failure caused by Beego session update problem (#3194)
* feat: fix the cas logout failure caused by beego session update problem

* fix: simplify the implementation of logout timer

* fix: change the location of the login success code

* fix: add i18n to CasLogout.js
2024-09-10 21:31:37 +08:00
943edfb48b feat: support QR login for casdoor app (#3190)
* feat: add MFA devices QR code to UserEditPage

* chore: remove mfa devices
2024-09-08 22:38:13 +08:00
0d02b5e768 feat: remove disabled state in syncer.table 2024-09-07 21:08:21 +08:00
ba8d0b5f46 feat: Revert "feat: Users added through LDAP cannot log in using the set password" (#3186)
This reverts commit 973a1df6c2.
2024-09-07 20:55:14 +08:00
973a1df6c2 feat: Users added through LDAP cannot log in using the set password (#3175)
* fix: login will prioritize the use of password set in casdoor and use ldap when use LDAP option in login form or user never change their password in casdoor after sync

* fix: promote if statement
2024-09-06 10:31:34 +08:00
05bfd3a3a3 feat: fix bug that custom SAML providers are removed by GetMaskedApplication() (#3165) 2024-09-05 20:08:56 +08:00
69aa3c8a8b feat: Revert "feat: add Casbin editor's checking in model editor" (#3167)
This reverts commit a1b010a406.
2024-09-03 21:59:06 +08:00
a1b010a406 feat: add Casbin editor's checking in model editor (#3166)
* feat: add model syntax linting and update dependencies

* refactor: move model linter logic to separate module
2024-09-03 21:32:45 +08:00
89e92cbd47 feat: when using basic auth to fetch access_token will return restful response to oidc client (#3164) 2024-09-03 08:05:29 +08:00
d4c8193357 feat: support reCAPTCHA v3 captcha provider (#3160)
* feat: support reCAPTCHA v3 captcha provider

* fix: modify the implementation of row component style in CaptchaModal.js
2024-09-02 22:15:03 +08:00
9b33800b4c feat: add email_verified, phone_number and phone_number_verified field for standard jwt token (#3156)
* feat: add email_verified, phone_number and phone_number_verified field for standard jwt token

* fix: fix linter err
2024-08-31 12:49:39 +08:00
ec98785172 feat: certEditPage will be redirected to 404 when name is changed (#3154) 2024-08-30 23:04:50 +08:00
45dd4cc344 feat: fix nonce not parsed issue in fastAutoSignin() (#3153)
* fix: fix nonce none passed when auto sign enabled

* fix: fix query error
2024-08-30 22:29:23 +08:00
1adb172d6b feat: add more crypto algorithm for jwt signing (#3150)
* feat: add more algorithm support for JWT signing

* feat: add i18n support

* feat: add i18n support

* feat: optimize if statement

* fix: remove additional space line
2024-08-30 16:59:41 +08:00
c08f2b1f3f feat: support Casdoor storage provider (#3147)
* feat: support Casdoor storage provider

* fix: fix code format and nil pointer error

* feat: change cert if statement
2024-08-27 23:54:03 +08:00
62bb257c6d feat: make Resource.Url length to 500 2024-08-26 23:57:41 +08:00
230a77e3e3 feat: add captcha page (#3144) 2024-08-26 23:22:53 +08:00
dce0a96dea feat: improve uploaded file URL 2024-08-26 21:41:28 +08:00
65563fa0cd feat: Ensure MFA email and phone are validated before enabling (#3143)
Added validation checks to ensure that a user's email and phone number are provided before enabling MFA email and phone respectively. This fixes the issue where MFA could be enabled without these values, causing inconsistencies.
2024-08-26 08:40:22 +08:00
f2a94f671a feat: complete i18n translation (#3141)
* feat: complete i18n translation

* fix: fix problem in cs/data
2024-08-24 23:27:59 +08:00
1460a0498f feat: support assign a default group for synchronized from external openldap (#3140)
* feat: support default sync group for ldap (with without add i18n translate)

* feat: improve translation

* feat: update all i18n translation

* revert: remove new i18n translation
2024-08-24 00:12:52 +08:00
adc63ea726 feat: fix wrong error alert in ApiFilter's getObject() 2024-08-23 23:36:55 +08:00
0b8be016c5 feat: add enableErrorMask config 2024-08-23 22:19:17 +08:00
986dcbbda1 feat: handle error in ApiFilter 2024-08-23 21:50:48 +08:00
7d3920fb1f feat: add ManagedAccounts to JWT 2024-08-20 22:23:58 +08:00
b794ef87ee feat: Revert "feat: support reCAPTCHA v3 captcha provider" (#3135)
This reverts commit a0d6f2125e.
2024-08-20 17:56:53 +08:00
a0d6f2125e feat: support reCAPTCHA v3 captcha provider (#3130) 2024-08-20 17:29:37 +08:00
85cbb7d074 feat: add replaceAll polyfill to be compatible with Firefox 68 2024-08-17 18:37:21 +08:00
fdc1be9452 feat: add provider.Bucket to fileUrl response and TrimPrefix "/" before delete GCS object (#3129)
* feat: add provider.Bucket to fileUrl response

* feat: TrimPrefix "/" before Google Cloud Storage delete object
2024-08-17 11:46:58 +08:00
2bd7dabd33 feat: allow custom Domain of Google Cloud Storage Provider (#3128) 2024-08-15 23:28:36 +08:00
9b9a58e7ac feat: update casdoor/oss version to support Google Cloud's Application Default Credentials (#3125) 2024-08-15 13:45:27 +08:00
38e389e8c8 feat: Pagination not updating after last item deletion (#3120) 2024-08-13 16:09:16 +08:00
ab5fcf848e feat: support accessKey and accessSecret login in AutoSigninFilter (#3117) 2024-08-12 12:20:41 +08:00
b4e51b4631 feat: improve error message in GetFailedSigninConfigByUser() 2024-08-10 09:31:46 +08:00
45e25acc80 feat: fix JWT generate issue cause by shared application (#3113)
* fix: fix jwt generate cause by shared application

* fix: fix built-in org will not add -org-
2024-08-09 22:48:44 +08:00
97dcf24a91 feat: improve error message in GetAuthorizationCodeToken() 2024-08-09 21:06:23 +08:00
4c0fff66ff feat: support shared application across organizations (#3108)
* feat: support share application

* revert: revert i18n

* fix: improve code format

* fix: improve code format and move GetSharedOrgFromApp to string.go
2024-08-09 15:43:25 +08:00
e7230700e0 feat: Revert "feat: fix Beego session delete concurrent issue" (#3105)
This reverts commit f21aa9c0d2.
2024-08-07 16:51:54 +08:00
f21aa9c0d2 feat: fix Beego session delete concurrent issue (#3103) 2024-08-07 16:29:35 +08:00
4b2b875b2d feat: Czech, Slovak localization (#3095)
* feat: add l10n Czech, Slovak language support

* feat: i18n Czech, Slovak translation
2024-08-02 09:39:47 +08:00
df2a5681cc feat: add missing account items in CheckPermissionForUpdateUser() (#3094) 2024-08-01 23:34:12 +08:00
ac102480c7 feat: support Radius Challenge/Response for MFA (RFC2865) feature request (#3093)
* feat: support RFC2865 for radius server when user enable TOTP mfa

* fix: fix linter err
2024-08-01 22:02:49 +08:00
feff47d2dc feat: skip agreement check when the terms are not visible (#3088) 2024-07-30 14:04:03 +08:00
79b934d6c2 feat: enforce acceptance of terms and conditions for social logins (#3087)
* feat: Enforce acceptance of terms and conditions for social logins (#2975)

* feat: add error message for agreement acceptance
2024-07-29 17:22:48 +08:00
365449695b fix: fix application field in invitationEditPage will use translation of "All" as value (#3085) 2024-07-29 01:35:28 +08:00
55a52093e8 feat: fix bug that user can signup without invitation code via OAuth (#3084)
* fix:fix user can signup without invitation code when using 3rd oauth

* fix:use correct i18n translation
2024-07-29 00:59:02 +08:00
e65fdeb1e0 feat: ABAC support for /api/batch-enforce endpoint (#3082) 2024-07-27 09:43:58 +08:00
a46c1cc775 feat: update WeCom OAuth URLs (#3080) 2024-07-26 22:03:24 +08:00
5629343466 feat: fix missing extendApplicationWithSigninMethods() in getDefaultApplication() (#3076) 2024-07-24 22:30:15 +08:00
3718d2dc04 feat: improve name mapping in LarkIdProvider (#3075)
* fix: change user identifier to the `user_id` field in IdP Lark, and use Chinese name to be the display name

* Update lark.go

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-07-23 21:12:53 +08:00
38b9ad1d9f feat: Add Support for memberOf Overlay in LDAP Server (#3068)
* feat: Allow All Users to Perform LDAP Search Lookups in their org

* feat: add ldap member of support
2024-07-21 01:25:42 +08:00
5a92411006 feat: add MFA accounts table (#3066)
* feat: add mfa accounts store

* fix: change MFA to Mfa

* fix: change MFA to Mfa

* fix: delete api
2024-07-20 22:51:15 +08:00
52eaf6c822 feat: Allow All Users to Perform LDAP Search Lookups in their org (#3064) 2024-07-20 20:44:29 +08:00
cc84709151 feat: add webhook support for invoice-payment and notify-payment (#3062) 2024-07-20 12:49:34 +08:00
22fca78be9 feat: fix bug in AdapterEditPage 2024-07-19 00:57:56 +08:00
DSP
effd257040 feat: fix isPasswordWithLdapEnabled logic in handleBind() for redirecting to other LDAP sources (#3059)
* Added parameters to function call in server.go

Added needed parameters for redirection to other LDAP sources to function correctly and not always run into the "wrong credentials" error

* Update server.go

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-07-18 21:04:17 +08:00
a38747d90e feat: fix bug in GetPolicies() 2024-07-18 18:40:55 +08:00
da70682cd1 feat: fix bug in obtaining Casdoor version in Docker (#3056) 2024-07-16 18:13:44 +08:00
4a3bd84f84 feat: fix the problem of abnormal tour when refreshing (#3054)
* fix: fix the problem of abnormal tour when refreshing

* fix: change the way enableTour configuration is stored
2024-07-12 19:27:55 +08:00
7f2869cecb feat: link transaction with balance and payment (#3052)
* feat: add and update transaction when recharging

* feat: add pay with balance

* feat: improve code format

* feat: update icon url for balance
2024-07-12 15:48:37 +08:00
cef2ab213b feat: add JWT-Standard format to fix oidc address type problem (#3050)
* feat: add JWT-Standard option to return standard OIDC UserInfo

* fix: fix error occurs by different claim type

* feat: improve code format and add missing return
2024-07-12 09:36:50 +08:00
cc979c310e feat: OAuth provider lark supports getting phone number (#3047) 2024-07-11 08:56:28 +08:00
13d73732ce fix: improve initBuiltInOrganization() 2024-07-10 14:18:30 +08:00
5686fe5d22 feat: use orgnization logo as tour logo and allow to configure whether to enable tour in organization edit page (#3046) 2024-07-10 14:18:04 +08:00
d8cb82f67a feat: upgrade CI Node.js version to 20 2024-07-09 13:09:40 +08:00
cad2e1bcc3 feat: don't drop empty table for adapters (#3043)
* fix: solve the problem of update operation returning 'unaffected'

* feat: remove the action for Dropping empty adapter data table
2024-07-09 11:35:22 +08:00
52cc2e4fa7 feat: fix bug in permission's owner edit (#3041) 2024-07-06 11:24:08 +08:00
8077a2ccba feat: fix bug for access key and secret login (#3022)
* fix: get username for keys

* chore: move user nil check
2024-06-27 21:24:54 +08:00
4cb8e4a514 feat: Revert "feat: fix OIDC address field" (#3020)
This reverts commit 2f48d45773.
2024-06-25 16:14:26 +08:00
2f48d45773 feat: fix OIDC address field (#3013)
* feat:add fields of sync-database

* feat:add fields of sync-database

* feat: add several fields related to the OIDC specification address

* feat: add the field Address to Address structure in UserWithoutThirdIdp

* fix: delete redundant fields

* fix: add Address struct and delete redundant fields
2024-06-25 11:54:34 +08:00
cff0c7a273 feat: support "Use Email as username" in org (#3002)
Signed-off-by: Grégoire Bélorgey <gregoire@jianda.fr>
2024-06-22 16:52:11 +08:00
793a7d6cda feat: add free charge price mode for product buy page (#3015)
* feat: add free charge price mode for product buy page

* fix: improve code format
2024-06-22 14:05:53 +08:00
4cc2120fed feat: fix the top Navbar UI is broken issue (#3000) 2024-06-09 17:05:04 +08:00
93b0f52f26 feat: Revert "feat: fix cannot create "/files" folder issue in local file storage provider in Docker" (#2997)
This reverts commit e228045e37.
2024-06-06 11:09:02 +08:00
e228045e37 feat: fix cannot create "/files" folder issue in local file storage provider in Docker (#2994) 2024-06-06 10:49:56 +08:00
6b8c24e1f0 feat: fix password not encrypted issue in SetPassword() API (#2990)
* fix: fix password not encrypted in set password and password type not changed

* Update user.go

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-06-04 13:32:13 +08:00
8a79bb64dd feat: test SMTP connection with browser parameters (#2986) 2024-06-04 01:34:36 +08:00
e5f9aab28f feat: support resetting password on first login (#2980)
* feat: support reset password in first login

* feat: disable needUpdatePassword when user haven't email and phone and mfa
2024-06-02 01:00:55 +08:00
7d05b69aac feat: remove useless code 2024-05-28 20:33:55 +08:00
868e66e866 feat: fix QQ login error when using mobile browser (#2971) 2024-05-27 01:07:15 +08:00
40ad3c9234 feat: support MFA fields in syncer (#2966)
* feat:add fields of sync-database

* feat:add fields of sync-database
2024-05-27 01:06:59 +08:00
e2cd0604c2 feat: add back arm64 support in Docker image (#2969) 2024-05-26 01:22:49 +08:00
78c3065fbb feat: fix address field bug in user edit page 2024-05-24 17:19:27 +08:00
af2a9f0374 feat: get phone number and country from Google OAuth provider (#2965)
* feat: get phone number and country from Google OAuth provider

* feat: fix i18n
2024-05-23 00:42:36 +08:00
bfcfb56336 feat: add address line 1 and 2 in web UI (#2961) 2024-05-19 23:55:38 +08:00
c48306d117 feat: check signup item email regex in signup page (#2960)
* feat: check email regex in frontend

* Update SignupPage.js

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-05-19 22:07:34 +08:00
6efec6b4b5 feat: support "label" field for signin item table (#2956) 2024-05-19 03:07:36 +08:00
2daf26aa88 feat: use lowercase username when isUsernameLowered is enabled (#2952)
* feat: auto trim username during login and lowercase when isUsernameLowered enabled in conf

* fix: fix linter error

* fix: fix linter error

* fix: fix linter error
2024-05-17 11:43:19 +08:00
21c151bcf8 feat: fix password not updated bug when updating syncer (#2945) 2024-05-13 00:12:35 +08:00
b6b0b7d318 feat: support checking whether send-webhook API has error (#2944)
* feat: add webhook response for record

* refactor: refactor SendWebhook and use readall to read response body

* fix: improve code format

* fix: improve code format

* fix: improve code format
2024-05-12 20:30:15 +08:00
0ecc1d599f feat: fix bug in AddUsersInBatch() 2024-05-11 16:59:33 +08:00
3456fc6695 fix: update go-sms-sender to v0.23.0 2024-05-10 14:05:53 +08:00
c302dc7b8e fix: fix bug when init plan and pricing and record (#2934)
* fix: fix potential bugs in init data

* fix: improve code format

* fix: fix bug when init plan and pricing and record
2024-05-07 23:33:01 +08:00
d24ddd4f1c feat: fix potential bugs in init_data.go (#2932)
* fix: fix potential bugs in init data

* fix: improve code format
2024-05-07 23:11:08 +08:00
572616d390 fix: fix bug in ProviderItem.CountryCodes 2024-05-07 17:17:45 +08:00
2187310dbc feat: fix bug in initDefinedOrganization() 2024-05-06 13:57:08 +08:00
26345bb21b feat: add sms provider sendcloud (#2927) 2024-05-06 13:38:55 +08:00
e0455df504 feat: improve record content masking (#2923)
* feat: hide password in record

* feat: improve code format

* feat: improve code format
2024-05-05 12:42:09 +08:00
1dfbbf0e90 feat: fix bug that fails to import built-in org via init_data.json (#2922) 2024-05-05 01:06:15 +08:00
d43d58dee2 feat: fix getProviders() owner bug in product edit page 2024-05-01 18:04:50 +08:00
9eb4b12041 fix: rename to countryCodes for UI 2024-05-01 11:44:21 +08:00
3a45a4ee77 fix: rename to countryCodes 2024-05-01 09:47:44 +08:00
43393f034b feat: fix the Email provider fails to match bug in GetProviderByCategoryAndRule() 2024-05-01 09:44:19 +08:00
bafa80513b fix: improve ProviderTable column UI 2024-05-01 00:46:48 +08:00
8d08140421 fix: fix typo in initBuiltInPermission() 2024-05-01 00:41:16 +08:00
3d29e27d54 feat: support multiple SMS providers for different regions (#2914)
* feat: support using different sms provider for different region

* feat: add multiple support for select and remove log

* feat: revert change for countryCode in loginPage

* feat: revert change for countryCode in user_util.go

* feat: revert change for countryCode in auth.go

* Update application_item.go

* Update CountryCodeSelect.js

* Update ProviderTable.js

---------

Co-authored-by: Yang Luo <hsluoyz@qq.com>
2024-05-01 00:40:47 +08:00
199f1d4d10 feat: fix Auto-login causing AuthCodeWithPKCE Failures (#2911) 2024-04-30 12:14:50 +08:00
227e938db6 feat: fix error behavior of custom oauth/saml provider in login page in big icon mode (#2900) 2024-04-26 23:33:41 +08:00
739cfd84ed feat: cannot empty SigninMethodTable now 2024-04-26 21:23:23 +08:00
8dbb041a34 feat: fix empty custom CSS for new rows in signin items table (#2897) 2024-04-24 15:19:30 +08:00
af2d26daf2 Add object.IsAppUser() 2024-04-24 01:10:38 +08:00
90d502ab2b feat: add custom css style for signup page and enhance css edit (#2880)
* feat: add custom css style for signup page and enhance css edit in signintable

* feat: change cssStyle to customCss

* feat: auto hide <style> label, fix display problem on providers, remove auto add providers in signup page

* fix: fix indent in signin items customCss and fix providers display in signup items

* fix: fix login replace logical
2024-04-21 11:56:18 +08:00
d51af3378e fix: fix init data not saved to database (#2885) (#2886) 2024-04-21 11:55:06 +08:00
87e2b97813 feat: translate Ukrainian language i18n 2024-04-20 02:14:23 +08:00
d9e44c1f2d fix: add "Is used" to verification list page 2024-04-20 00:18:52 +08:00
dfa4503f24 feat: support "mfa_phone_enabled", "mfa_email_enabled" in update-user API 2024-04-20 00:16:45 +08:00
f7fb32893b fix: close file in LocalFileSystemProvider's Put() (#2882) 2024-04-20 00:11:52 +08:00
66d0758b13 feat: fix DisableVerificationCode bug about empty email and phone 2024-04-19 13:28:13 +08:00
46ad0fe0be Improve Email Send() logic 2024-04-11 19:09:48 +08:00
6b637e3b2e feat: fix SendgridEmailProvider error handling, fix send-email template 2024-04-11 00:18:39 +08:00
3354945119 feat: add SendGrid Email provider (#2865)
* feat: add support for email provider send grid

* feat: rename send grid to sendgrid

* feat: rename send grid to sendgrid

* feat: change logo url of send grid
2024-04-09 22:16:01 +08:00
19c4416f10 feat: degrade the ant-design/cssinjs version to fix the Chrome 87 broken UI issue (#2861) 2024-04-09 09:15:39 +08:00
2077db9091 fix: fix bug in VerificationListPage 2024-04-07 15:39:25 +08:00
800f0ed249 feat: add tzdata package in Dockerfile to fix timezone issue (#2857)
Add tzdata to resolve possible time zone errors
2024-04-07 14:27:45 +08:00
xyt
6161040c67 fix: Dismiss google one tap after logged in by setting disableCancelOnUnmount to false (#2854)
* fix: Google One Tap should be hidden after logged in

* Change the call location for google.accounts.id.cancel()

* fix: hide google one tap after login by set disableCancelOnUnmount to false
2024-04-05 23:39:33 +08:00
xyt
1d785e61c6 feat: Google One Tap should be hidden after logged in (#2853)
* fix: Google One Tap should be hidden after logged in

* Change the call location for google.accounts.id.cancel()
2024-04-05 20:10:13 +08:00
0329d24867 feat: add isUsernameLowered to config 2024-04-02 21:54:16 +08:00
fb6f3623ee feat: add requireProviderPermission() 2024-03-30 23:24:59 +08:00
eb448bd043 fix: fix permission problem in provider (#2848) 2024-03-30 23:18:03 +08:00
xyt
ea88839db9 feat: add back button in forget password page (#2847)
* feat: add back button in forget password page

* fix: can't step back when directly entering forgot password page

* feat: forget password page always return to login page

* feat: if has history then go back to history & change style

* Update ForgetPage.js

* fix: reset button position

* Update ForgetPage.js

* Update ForgetPage.js

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-30 23:17:47 +08:00
cb95f6977a fix: fix PasswordModal error when changing username 2024-03-30 12:28:55 +08:00
9067df92a7 feat: revert "feat: Support metamask mobile login" (#2845)
This reverts commit bfa2ab63ad.
2024-03-30 00:36:25 +08:00
bfa2ab63ad feat: Support metamask mobile login (#2844) 2024-03-30 00:08:52 +08:00
505054b0eb feat: use minWidth for a better display effect in org select (#2843) 2024-03-29 15:47:27 +08:00
f95ce13b82 fix: support "Email or Phone" in signup table 2024-03-29 09:07:37 +08:00
xyt
5315f16a48 feat: can specify UI theme via /?theme=default and /?theme=dark (#2842)
* feat: set themeType through URL parameter

* Update App.js

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-29 00:52:18 +08:00
d054f3e001 feat: The /login/oauth/access_token api supports the token and id_token grant types. (#2836)
* In the response of the /api/get-captcha endpoint, add the parameters "owner" and "name" because these two parameters will be used when calling the /api/verify-captcha endpoint.

* The /login/oauth/access_token api supports the token and id_token grant types.
2024-03-28 00:41:54 +08:00
b158b840bd Add "new-user" to webhook event list 2024-03-27 15:23:06 +08:00
b16f1807b3 fix: fix bug in "new-user" record 2024-03-27 15:15:40 +08:00
d0cce1bf7a Order by "id" in GetPaginationRecords() 2024-03-27 15:14:41 +08:00
9892cd20ab Improve erorr message in CheckVerificationCode() 2024-03-27 15:14:20 +08:00
d1f31dd327 feat: fix linter 2024-03-26 23:24:53 +08:00
94743246a1 Improve "%{user.friendlyName}" handling 2024-03-25 21:26:36 +08:00
39ad1bc593 Add signup's object in AfterRecordMessage() 2024-03-25 21:20:33 +08:00
d97f833d2a feat: Add 'owner' and 'name' Parameters to /api/get-captcha Response for /api/verify-captcha Usage (#2834) 2024-03-25 16:34:42 +08:00
948fa911e2 feat: add users to getGroups() and getGroup() APIs 2024-03-22 23:32:30 +08:00
6073a0f63d Rename GroupListPage and GroupEditPage 2024-03-22 23:14:05 +08:00
91268bca70 Improve enableAutoSignin option UI 2024-03-22 22:55:10 +08:00
23dbb0b926 feat: add response to Records page (#2830)
* feat: add response to Records page

* feat: improve AddRecord

* feat: remove log and return err

* feat: improve record in signup and record deny

* fix: filter will generate 403 record correctly
2024-03-22 14:53:38 +08:00
97cc1f9e2b fix: delete duplicate err check in utils/validation.go (#2831) 2024-03-21 18:17:38 +08:00
8c415be7c7 feat: upgrade goth to v1.79.0 2024-03-20 19:57:15 +08:00
e87165cfc8 Upgrade go.mod versions 2024-03-20 19:51:56 +08:00
fc4fa2e8b6 feat: add verification list page and related API (#2822)
* feat: add verification list page and relevant api

* feat: improve code format

* fix: fix timestamp display error
2024-03-19 19:10:52 +08:00
44ae76503e feat: add default user mapping in custom oauth2 provider (#2819) 2024-03-18 23:01:17 +08:00
ae1634a4d5 feat: fix user cannot logout issue about bug in GetSessionToken() 2024-03-18 02:11:39 +08:00
bdf9864f69 fix: add FaceIdSigninBegin() to verify user information before face login (#2815)
* feat: add FaceIdSigninBegin() to verify user information before face login

* Update face.go

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-18 00:04:12 +08:00
72839d6bf5 feat: fix TokenFormat error in get-account API 2024-03-17 23:03:50 +08:00
2c4b1093ed fix: Correct expiresIn calculation for WeChat Mini Program token. (#2814) 2024-03-17 22:20:21 +08:00
d1c55d5aa7 fix: improve error message in token_cas.go 2024-03-17 22:01:49 +08:00
c8aa35c9c6 feat: add token to the page for Chrome extension (#2804)
* feat: add token to the page for Chrome extension

* Update token_oauth.go

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-17 22:01:28 +08:00
6037f37b87 feat: add default token format for built-in app 2024-03-17 20:46:01 +08:00
1b478903d8 feat: fix login page error cannot show bug 2024-03-17 11:39:12 +08:00
4f5ac7a10b Fix Face IDs label 2024-03-17 09:56:24 +08:00
e81ba62234 Improve Face ID signin method UI 2024-03-17 09:56:23 +08:00
Ron
a19060c7cb fix: missing parameter type_token_hint in IntrospectToken() (#2812)
* fix: missing parameter type_token_hint in IntrospectToken(); fix key token type
2024-03-17 01:39:04 +08:00
96812f676b fix: "fs" module not found issue in face-api.js for browser usage (#2810) 2024-03-17 01:35:43 +08:00
04f0458b5c feat: improve handleCameraError() and camera call logic (#2809)
add i18n for face recognition
2024-03-16 22:52:57 +08:00
fd0bcd9a17 Improve getObject() for "/api/get-policies" 2024-03-16 21:42:00 +08:00
01a5958307 Improve error text in RequireAdmin() 2024-03-16 21:14:19 +08:00
be88b00278 feat: improve RequireAdmin() logic 2024-03-16 20:49:17 +08:00
1bd0245e7a Improve CheckVerificationCode() error message, add receiver to index 2024-03-16 18:16:29 +08:00
cc84bd37cf Add object field in RecordListPage 2024-03-16 16:57:04 +08:00
8302fcf805 Improve handleCameraError() 2024-03-16 09:55:55 +08:00
391a533ce1 feat: add "Face ID" login method (#2782)
Face Login via face-api.js
2024-03-16 09:04:00 +08:00
57431a59ad fix: Ensure /api/get-app-login Returns Captcha Provider for Applications Configured with Captcha (#2800)
In LoginPage.js, the line 92:const captchaProviderItems = this.getCaptchaProviderItems(this.props.application); 

captchaProviderItems have no Captcha Provider.
2024-03-15 19:56:12 +08:00
88a4736520 feat: fix GetDashboard() page 2024-03-15 19:52:19 +08:00
2cb6ff69ae fix: show selected organizations' statistics in dashboard page (#2805)
* fix: show selected organizations' statistics in dashboard page

* Update get-dashboard.go

* Update saml_idp.go

---------

Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-15 19:36:39 +08:00
e1e5943a3e fix: fix the issue of adding xmlns="" when generating XML (#2799)
* fix:solve the problem of adding xmlns="" when generating XML

* fix:remove fmt.Println

* Update saml_idp.go

---------

Co-authored-by: zhaoxianfei <zhaoxianfei@meiqia.cn>
Co-authored-by: Eric Luo <hsluoyz@qq.com>
2024-03-13 23:59:05 +08:00
3875896c1e feat: support custom header logo (#2801)
* feat: support custom header logo

* feat: add i18n

* feat: preview default logo when field is empty

* feat: improve logo setting and display logic

* feat: change logoLight to logo
2024-03-13 23:33:43 +08:00
7e2f265420 feat: improve organization select UI (#2798) 2024-03-12 19:39:53 +08:00
53ef179e9b Set Webhook.Url length to 200 2024-03-11 18:18:01 +08:00
376ef0ed14 feat: support custom Email content in /send-email API 2024-03-11 11:48:00 +08:00
ca183be336 Improve ManagedAccountTable UI 2024-03-11 00:13:34 +08:00
e5da57a005 feat: fix cert's ES options 2024-03-10 19:30:05 +08:00
e4e225db32 Use "ES512" value 2024-03-10 19:25:41 +08:00
a1add992ee Support legacy "RSA" value 2024-03-10 19:23:54 +08:00
2aac265ed4 Improve populateContent() 2024-03-10 18:58:53 +08:00
2dc755f529 fix: add more cert algorithms like ES256 and PS256 (#2793) 2024-03-10 18:39:41 +08:00
0dd474d5fc feat: fix public profile page shows blank page bug 2024-03-10 14:12:24 +08:00
6998451e97 fix: support roles and permissions in /userinfo API 2024-03-10 12:34:56 +08:00
9175e5b664 Fix bug in GetMaskedEmail() 2024-03-10 11:49:55 +08:00
dbc6b0dc45 feat: fix issue that forget password page fails to redirect back to signin page (#2792) 2024-03-10 09:55:44 +08:00
31b7000f6a fix: enable the only language for login page 2024-03-09 11:28:23 +08:00
d25eaa65cd feat: support custom page footer (#2790) 2024-03-08 23:11:03 +08:00
f5bcd00652 Add language to records page 2024-03-08 23:03:30 +08:00
0d5f49e40a fix: fix GetResources() bug for app users 2024-03-08 16:15:31 +08:00
3527e070a0 Fix my account page UI 2024-03-08 15:18:18 +08:00
0108b58db4 Return status 200 for unauthorized operation, revert commit: 2fd2d88d20 2024-03-08 15:11:25 +08:00
976b5766a5 feat: refactor out token_oauth.go 2024-03-08 15:03:28 +08:00
a92d20162a feat: show all resources for org admin 2024-03-08 15:03:03 +08:00
204b1c2b8c Fix resource page link error 2024-03-08 14:44:39 +08:00
49fb269170 Improve error handling for GetSamlResponse() 2024-03-08 02:17:50 +08:00
c532a5d54d Remove suspense fallback loading. 2024-03-07 23:21:25 +08:00
89df80baca feat: remove loading fallback in Suspense and use spin to display (#2780) 2024-03-06 20:30:54 +08:00
d988ac814c fix: fix account items display error (#2781) 2024-03-06 20:30:34 +08:00
e4b25055d5 Improve isAllowedInDemoMode() 2024-03-06 02:17:28 +08:00
4123d47174 feat: callback will jump to blank page when from param start with "http" (#2778) 2024-03-06 01:07:52 +08:00
fbdd5a926d Fix normal user my-account page blank bug 2024-03-06 01:07:28 +08:00
92b6fda0f6 feat: support more objects in init_data JSON (#2776) 2024-03-05 23:41:46 +08:00
6a7ac35e65 fix: fix wechat media account can not bind issue (#2774)
* fix: fix wechat media account can not bind

* fix: improve code format
2024-03-05 18:46:28 +08:00
fc137b9f76 feat: fix custom JS doesn't reload after refresh bug (#2773) 2024-03-05 15:03:25 +08:00
11dbd5ba9a fix: fix duplicated load bug of custom JS (#2771) 2024-03-05 00:09:37 +08:00
19942a8bd4 Add webhook.SingleOrgOnly 2024-03-04 21:14:52 +08:00
f9ee8a68cb Support Chrome extension redirecting 2024-03-04 18:31:56 +08:00
Ron
f241336ad7 feat: add OSON SMS provider (#2769)
* implemented SMS provider 'OSON SMS' for frontend

* feat: add 'OSON SMS' provider for frontend
2024-03-04 01:05:53 +08:00
8b64d113fb Upgrade go-sms-sender dependency to 0.20.0 2024-03-04 01:05:28 +08:00
a8800c4d5c fix: add missing / for style tag in signin items (#2768) 2024-03-03 23:46:57 +08:00
75fc9ab9f7 Improve GetMaskedApplication()'s logic 2024-03-03 22:01:49 +08:00
d06da76c3d feat: fix bug in /get-organization-applications API 2024-03-03 21:08:36 +08:00
bc399837cc Rename label to "Custom CSS" 2024-03-03 20:45:14 +08:00
265abfe102 fix: handle error in storage.GetStorageProvider() 2024-03-03 18:18:54 +08:00
12acb24dbc feat: add transaction pages (#2761) 2024-03-02 10:41:16 +08:00
ba1ddc7e50 fix: admin can modify user ID now 2024-02-28 18:07:53 +08:00
59e07a35aa Add balance to user 2024-02-28 16:54:30 +08:00
cabe830f55 feat: use dynamic import to load web3Auth (#2757)
* feat: use dynamic import to load web3Auth and success reduce the size of signin page to 720KB when web3 idp disabled

* feat: avoid frequent import in OAuthWidget.js which may cause e2e test EPIPE error

* feat: remove import may cause e2e error

* feat: remove import may cause e2e error

* feat: remove bug may cause e2e error

* feat: try use chrome in ci/cd instead of electron to solve e2e error
2024-02-28 15:58:04 +08:00
78af5daec3 feat: use resourcesToBackend to load i18n files (#2755) 2024-02-28 01:43:55 +08:00
6c76913f71 fix: Set default value for email and SMS rule to all instead of none (#2754) 2024-02-28 01:28:59 +08:00
5a0d1bcb6e Support login by user ID 2024-02-28 01:28:24 +08:00
37232faa07 feat: fix bug for missing SMS and Email provider in application 2024-02-27 22:54:35 +08:00
4d9c81ef96 Fix broken error messages 2024-02-27 22:48:33 +08:00
b0d87f60ae feat: use lazy load to load management pages (#2752) 2024-02-27 22:31:02 +08:00
a5499219d1 fix: refactor out ManagementPage.js from App.js (#2750)
* feat: basic separate

* feat: nearly fully separate

* feat: add License

* feat: full load application in /login url, lazy load in /login/oauth... etc

* fix: fix onChangeTheme error in organization edit page

* fix: revert lazy load
2024-02-27 18:49:23 +08:00
6a813a1f8c feat: fix headerHtml script not running bug (#2749)
* fix: fix custom head not exec <script> tag

* fix: fix create element bug
2024-02-26 20:21:07 +08:00
e4cf244cf8 fix: theme will fully restore after page reload (#2743)
* fix: theme will set to default after flush

* fix: use consume theme to ensure EntryPage will always use default themeAlgorithm

* fix: fix logo render, add try catch to handle
potential err cause by JSON.parse
2024-02-25 00:05:13 +08:00
f5a6415e57 feat: improve dark theme UI (#2742) 2024-02-24 20:11:42 +08:00
13e871043c fix: fix theme switch bug (#2741) 2024-02-24 16:56:12 +08:00
a8699d0b87 feat: use React routing to remove spin between signup and signin pages (#2740)
* fix: Regarding the color of loading

* fix: use goToLinkSoft and use same code format with result and forget psw

* fix: update signup url
2024-02-24 12:59:09 +08:00
6621d693de feat: revert "feat: use i18next-resources-to-backend to lazy load i18n" (#2739)
This reverts commit dc3131c683.
2024-02-23 23:38:49 +08:00
dc3131c683 feat: use i18next-resources-to-backend to lazy load i18n (#2738)
* feat: use i18next-resources-to-backend to lazy load i18n file

* feat: change source in yarn.lock
2024-02-23 22:35:59 +08:00
042a8d0ad6 feat: add rule for SMS and Email provider (#2733)
* add phonecoderule

* feat:add phone code rule

* feat: add email rule

* fix: merge
2024-02-23 00:09:37 +08:00
44abfb3430 feat: support custom header HTML in entry pages (#2731) 2024-02-22 17:56:47 +08:00
53b8424a1f feat: fix JSON typo in init_data.json template 2024-02-21 17:33:08 +08:00
23c2ba3a2b feat: support ssh key/pem file in DB syncer (#2727)
* feat: support connect database with ssh tunnel in syncer

* feat: improve i18n translate

* feat: improve code format and i18n
2024-02-21 17:27:37 +08:00
3a9ffedce4 feat: support phone and Email in /api/login/oauth/access_token API (#2725)
Phone Number supports for /api/login/oauth/access_token as username

 Closes: #2724
2024-02-21 17:27:24 +08:00
03f005389f feat: fix organizationChangeTrigger() and userChangeTrigger() bugs 2024-02-21 01:14:32 +08:00
69a8346d05 Remove "/auto-signup/oauth/authorize" path introduced in PR: #896 2024-02-20 17:40:39 +08:00
546512a0ea Fix getCasvisorApplication() 2024-02-20 13:45:03 +08:00
c4a307b9ec feat: add built-in "Records" pages back (#2720) 2024-02-20 13:28:29 +08:00
d731c3c934 feat: add regex support for account item (#2714)
* feat: add regex support for account item

* feat: use reflect to process user field

* fix: fix lint problem

* feat: improve code format and fix reflect error
2024-02-17 15:24:36 +08:00
4a68dd65cd Fix typo in renderFormItem() 2024-02-16 10:13:50 +08:00
d59148890e Improve error handling for CheckVerificationCode() 2024-02-16 08:53:56 +08:00
7f52755e32 feat: improve error messages 2024-02-16 01:13:34 +08:00
eaa6f50085 Add initial value for grantTypes 2024-02-15 23:18:23 +08:00
f35a5f9a47 feat: fix issue that admin cannot enable MFA for user (#2702) 2024-02-14 23:29:04 +08:00
7481b229a4 feat: show domain field for MinIO storage provider 2024-02-14 13:54:17 +08:00
39e485ae82 Fix SigninTable issue 2024-02-14 12:20:03 +08:00
764c64e67c Fix SigninTable CSS 2024-02-14 12:10:30 +08:00
e755a7331d Fix renderLink() 2024-02-14 09:45:21 +08:00
6d9d595f86 fix: Revert "fix: fix display bug in SigninTable" (#2700)
This reverts commit d52058d2ae.
2024-02-14 09:44:42 +08:00
d52058d2ae fix: fix display bug in SigninTable (#2698)
* fix: fix display bug in SigninTable

* fix: fix code bug

* feat: improve code format

* feat: improve code format
2024-02-14 09:26:51 +08:00
bcfbfc6947 Support "signinUrl" in forget page 2024-02-14 02:36:52 +08:00
75699c4a26 feat: improve code in getObject() 2024-02-13 23:50:21 +08:00
3e8bfb52a8 feat: add signin items table (#2695)
* feat: add signin items table

* fix:unable to login

* feat: improve code format

* fix: fix display err on signup link

* feat: improve display of sign up link
2024-02-13 23:12:40 +08:00
bbbd857a45 fix: fix bug that failed to run initApi adapter in docker (#2696) 2024-02-13 23:12:25 +08:00
498900df76 feat: allow dot in the username (like john.smith) (#2692) 2024-02-12 20:52:17 +08:00
7e3c1a6581 fix: improve goth code (#2693)
Signed-off-by: Dmitrii Aleksandrov <goodmobiledevices@gmail.com>
2024-02-12 20:51:58 +08:00
6e28043dba refactor: New Crowdin translations (#2648)
* refactor: New Crowdin translations by Github Action

* refactor: New Crowdin Backend translations by Github Action

---------

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-02-12 18:54:31 +08:00
cb200687dc feat: fix GetUserByUserId() API crash issue 2024-02-12 18:51:55 +08:00
23bb0ee450 feat: fix error handling in AdfsIdProvider (#2687) 2024-02-10 15:38:38 +08:00
117259dfc5 ci: fix repo name in CI 2024-02-10 15:38:17 +08:00
e71d0476f0 feat: support data initialization for groups, adapters, enforcers, plans and pricings (#2685) 2024-02-08 20:46:40 +08:00
b5d26767b2 docs: improve README 2024-02-08 00:02:31 +08:00
5c4e22288e feat: improve error handling and code format (#2682)
* feat: improve error process and code format

* feat: improve error process and code format
2024-02-07 20:55:33 +08:00
3ac4be64b8 fix: error msg for invalid org & app names in signup (#2679) 2024-02-07 08:53:50 +08:00
97db54b6b9 feat: full support for wechat official account login (#2677)
* feat: full support for wechat official account login

* feat: improve provider edit page

* fix: improve i18n format
2024-02-07 00:00:10 +08:00
3a19d4c7c8 fix: do not filter webhooks by org 2024-02-06 20:33:11 +08:00
a60be2b2ab feat: refactor MFA code and fix no-session bug (#2676)
* refactor: refactor mfa

* refactor: refactor mfa

* refactor: refactor mfa

* lint

* chore: reduce wait time
2024-02-06 20:17:59 +08:00
06ef97a080 feat: can delete the whole SigninMethodTable 2024-02-06 16:43:16 +08:00
167c1b0f1b feat: fix bug in WeChat OA login (#2674)
* fix: fix the problem of Wechat Official Account login

* fix: fix code format problem

* fix: add error display and fix the code format problem

* fix: i18n problem and code format
2024-02-05 21:38:12 +08:00
7d0eae230e fix: fix /signup organization parameter issue (#2669) 2024-02-03 11:47:36 +08:00
901867e8bb feat: fix /signup parameter issue 2024-02-03 10:00:47 +08:00
b7be1943fa feat: Add Invitation Code to Generate Invitation Link (#2666)
Add auto-population of invitation fields in the registration page based on the invitation code in the link
2024-02-02 21:12:56 +08:00
bbbda1982f feat: fix missing MFA session issue (#2667) 2024-02-02 10:23:17 +08:00
e593f5be5b fix: improve code format (#2665)
* feat: replace io/ioutils pacakage with io/os package

* fix: add missing error handling
2024-02-01 23:06:12 +08:00
0918757e85 feat: add template support for Custom HTTP SMS provider (#2662) 2024-02-01 17:50:22 +08:00
ce0d45a70b feat: support SAML POST binding (#2661)
* fix: support saml http post binding

* fix: support saml http post binding

* fix: support saml post binding sp
2024-02-01 17:28:56 +08:00
c4096788b2 feat: ABAC support for /api/enforce endpoint (#2660) 2024-01-31 23:14:55 +08:00
523186f895 feat: Support sha512 password encryption algorithm (#2657)
* add sha512 encryption support for password

* fead: add sha512 encryption support for password
2024-01-31 00:06:06 +08:00
ef373ca736 feat: add deletedTime to user (#2652) 2024-01-30 23:18:32 +08:00
721a681ff1 fix: improve error handling in GetUserApplication() 2024-01-30 21:40:39 +08:00
8b1c4b0c75 feat: make phone field longer to 100 2024-01-30 19:06:18 +08:00
540f22f8bd feat: refactor GetTokenByTokenValue() 2024-01-29 10:03:33 +08:00
79f81f1356 Improve error handling in IntrospectToken() 2024-01-29 09:58:40 +08:00
4e145f71b5 feat: improve MFA UI and jump URL (#2647)
* fix: mfa UI

* fix: mfa UI
2024-01-28 16:46:35 +08:00
104f975a2f fix: fix wrong org issue for user's "signupApplication" 2024-01-28 01:51:03 +08:00
71bb400559 feat: support using org's defaultAvatar when adding user in web UI 2024-01-28 01:07:20 +08:00
93c3c78d42 feat: support "id_card" in UpdateUser() 2024-01-26 08:23:55 +08:00
dd51bbbabf feat: fix autoComplete for MFA passcode and SMS code (#2642)
* update: mfa autoComplete="off"

* Update SendCodeInput.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2024-01-23 19:52:16 +08:00
5318519bf8 fix: fix bug in LDAP user login error count (#2636)
Fix the issue where the login error count is not reset to 0 after a successful LDAP user login.
2024-01-22 13:42:11 +08:00
d7c40459c0 feat: implement the enforcement for new invitation page (#2628)
Added new invitation code implementation
2024-01-22 02:25:13 +08:00
de2932b5fb feat: use standalone Twitter OAuth provider instead of goth (#2632) 2024-01-20 21:49:02 +08:00
f4c873ffe6 Fix user profile page UI 2024-01-20 19:28:43 +08:00
97c7f2631a feat: fix organization.IsProfilePublic issue 2024-01-20 16:00:04 +08:00
93f0425759 Remove old application's InvitationCodes 2024-01-20 10:58:08 +08:00
6a00657e42 feat: fix forbidden and soft-delete check in forget password page 2024-01-19 22:13:02 +08:00
88130bf020 feat: add forbidden check in SetPassword() 2024-01-19 16:30:22 +08:00
5e99007fc9 Update goth to v1.78.0 2024-01-19 16:09:32 +08:00
66aca3124c fix: improve error handling in LarkIdProvider 2024-01-19 15:37:15 +08:00
61deb75c84 refactor: New Crowdin translations (#2512)
* refactor: New Crowdin translations by Github Action

* refactor: New Crowdin Backend translations by Github Action

---------

Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2024-01-18 22:18:51 +08:00
b8db07db4d feat: enable GetMaskedSyncers() 2024-01-18 20:59:27 +08:00
a681c267b3 Refactor code format 2024-01-18 20:53:04 +08:00
5fb6ea0ab4 Fix "password" tab in SigninMethods 2024-01-18 20:17:05 +08:00
0f6b7984d4 feat: improve isAllowedInDemoMode() 2024-01-17 13:07:44 +08:00
ba9d6e5d78 Fix Swagger API version 2024-01-16 00:09:28 +08:00
a4524e9996 fix: fix Swagger @Tag 2024-01-15 23:35:40 +08:00
b469928780 Fix Swagger @router 2024-01-15 23:27:42 +08:00
dc6fe13f75 feat: use signupItem.Regex to check signup page 2024-01-15 18:12:38 +08:00
8227762988 Support more special chars in password validating 2024-01-15 18:12:38 +08:00
d92b072ed0 feat: revert PR: "feat: more RFC like LDAP server behaviour" (#2611) 2024-01-15 13:58:33 +08:00
1161310f81 feat: improve README.md 2024-01-15 10:14:01 +08:00
48ba5f91ed feat: add Synology NAS storage provider (#2605) 2024-01-14 22:38:31 +08:00
53df2c2704 fix: add semantic versioning for helm charts (#2603) 2024-01-14 09:44:16 +08:00
78066da208 Improve setCorsHeaders() for "include" mode 2024-01-13 23:46:05 +08:00
60096468fe fix: fix CI email 2024-01-13 18:12:52 +08:00
39d6bc10f7 Fix GetCaptchaStatus() crash if not logged in 2024-01-13 18:04:38 +08:00
177f2f2f11 Add userId param to GetAllObjects() API 2024-01-13 18:03:40 +08:00
79b393afee feat: add regex to SignupTable 2024-01-13 16:08:49 +08:00
5bb12a30d4 Don't show two errors in verificationCode login page 2024-01-13 16:01:22 +08:00
fdb68bf9c8 Rename to SigninMethodTable 2024-01-13 15:53:01 +08:00
37748850c8 Fix nameFormat in SamlItem 2024-01-13 15:32:49 +08:00
8968396ae5 Fix bug in getDefaultLoginMethod() 2024-01-13 12:13:09 +08:00
f5395f15f9 feat: fix isSigninMethodEnabled() bug in frontend 2024-01-13 11:35:06 +08:00
73e44df867 Improve GetAllRoles() error handling 2024-01-13 10:06:08 +08:00
0b575ccf84 Refactor getAllValues() 2024-01-13 09:58:55 +08:00
9b7f465a47 Fix failedSigninFrozenTime typo 2024-01-13 02:12:29 +08:00
b1fe28fb83 Refactor application.FailedSigninLimit code 2024-01-13 02:09:18 +08:00
530d054adb feat: ci should commit index.yaml and push to docker hub (#2600) 2024-01-11 16:10:08 +08:00
a2b9f9baaf feat: support "JWT-Custom" to customize user properties inside access token (#2594)
* feat: add custom attribute to access token

* Update token_jwt.go

---------

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

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

* feat(actions): explicit checkout helm repo

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

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

* Update application.go

---------

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

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

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

* fix:fix comment and change some content for successfully generating typescript-axios sdk
2023-12-29 15:12:40 +08:00
c4819602ec fix: add mfa API to isAllowedInDemoMode() 2023-12-26 20:06:27 +08:00
dea03cdd15 feat: replace deprecated github.com/RobotsAndPencils/go-saml (#2558)
The `github.com/RobotsAndPencils/go-saml` has been officially deprecated
and archived on 7 June 2023.

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2023-12-25 21:15:53 +08:00
21f394847e fix: fix Swagger docs by improving comments 2023-12-23 11:57:18 +08:00
9bef9691fb feat: fix volcengine SMS provider error handling 2023-12-22 20:49:45 +08:00
141f22a707 feat: upgrade to Node.js 18 and Go 1.20 in Dockerfile 2023-12-22 14:46:41 +08:00
02329d342a feat: fix bug in "*" users and roles in permission edit page. 2023-12-22 14:16:00 +08:00
b9d3e2184c fix: update CI node version from 16 to 18 2023-12-22 09:28:45 +08:00
28caf8550e Support token parsed result 2023-12-22 02:04:25 +08:00
79159dc809 Improve TokenEditPage 2023-12-22 00:44:34 +08:00
63081641d6 Improve i18n text 2023-12-22 00:25:46 +08:00
698f24f762 feat: fix template code bug in SMS provider of Amazon SNS 2023-12-21 23:32:55 +08:00
5499e62d7f feat: add the FailedSigninLimit and FailedSigninfrozenTime configuration options to the application (#2552)
Add configuration items to the application to limit the number of logins and the login wait time after the maximum number of errors is reached
feat: #2272

fix: fixed the issue where the token parameter could be set to a negative value
2023-12-20 22:29:53 +08:00
f8905ae64c Fix S3-compliant storage providers support 2023-12-20 14:38:32 +08:00
a42594859f feat: improve enforce() and batchEnforce() API response 2023-12-20 11:41:54 +08:00
46e0bc1a39 Improve i18n texts 2023-12-20 10:09:00 +08:00
ffe2330238 Fix tag field in user list page 2023-12-20 01:57:56 +08:00
ec53616dc8 Update README.md 2023-12-20 01:52:29 +08:00
067276d739 Add new B2C provider 2023-12-17 16:29:29 +08:00
468ceb6b71 Fix get-all-objects API 403 issue 2023-12-15 21:32:45 +08:00
b31a317585 feat: add helm release github action (#2546) 2023-12-15 19:30:10 +08:00
396b6fb65f feat: refactor custom HTTP related filenames 2023-12-15 00:06:05 +08:00
be637fca81 fix: fix wrong POST param logic in custom HTTP providers 2023-12-15 00:00:47 +08:00
374928e719 feat: add custom HTTP Email provider (#2542)
* feat: implement Custom HTTP Email provider

* Update Setting.js

* Update ProviderEditPage.js

* Update http.go

* Update provider.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-12-14 22:35:25 +08:00
5c103e8cd3 Improve error handling in GenerateIdForNewUser() 2023-12-14 10:12:00 +08:00
85b86e8831 fix: dropped object group errors (#2545) 2023-12-14 09:00:25 +08:00
08864686f3 feat: fix Google cloud storage provider bug 2023-12-14 00:25:50 +08:00
dc06eb9948 feat: fix secret information issue in the CAPTCHA provider code (#2531) 2023-12-11 18:01:56 +08:00
b068202e74 Improve Radius username handling 2023-12-11 18:01:28 +08:00
cb16567c7b feat: helm support extra containers (#2530) 2023-12-10 14:41:56 +08:00
4eb725d47a Improve image upload UI 2023-12-08 19:42:20 +08:00
ce72a172b0 feat: add back Custom HTTP SMS provider 2023-12-07 16:59:41 +08:00
5521962e0c feat: update go-sms-sender to v0.17.0 to improve error handling 2023-12-07 14:25:21 +08:00
37b8b09cc0 feat: update go-sms-sender to v0.16.0 to fix first number missing bug in AmazonSNSClient.SendMessage 2023-12-06 20:05:48 +08:00
482eb61168 feat: improve StaticFilter() 2023-12-05 18:33:06 +08:00
8819a8697b feat: fix dropped error in stripe.go (#2525) 2023-12-05 16:02:33 +08:00
85cb68eb66 feat: unbind LDAP clients if not used any more 2023-12-02 17:51:25 +08:00
b25b5f0249 Support original accessToken in token APIs 2023-12-02 16:56:18 +08:00
947dcf6e75 Fix "All" roles bug in permission edit page 2023-12-02 15:26:52 +08:00
113c27db73 Improve logout's id_token_hint logic 2023-12-02 02:13:34 +08:00
badfe34755 feat: add "nonce" into the OAuth and OIDC tokens, for some apps require "nonce" to integrate (#2522) 2023-12-01 18:29:39 +08:00
a5f9f61381 feat: add token hash to improve performance 2023-11-30 18:05:30 +08:00
2ce8c93ead feat: Improve LDAP filter support (#2519) 2023-11-26 23:11:49 +08:00
da41ac7275 Improve error handling in getFaviconFileBuffer() 2023-11-25 18:31:33 +08:00
fd0c70a827 feat: Revert "feat: fix login page path after logout" (#2516)
This reverts commit 23d4488b64.
2023-11-24 15:52:59 +08:00
c4a6f07672 Allow app user in demo mode 2023-11-24 01:04:23 +08:00
a67f541171 feat: in LDAP, search '*' should return all properties (#2511) 2023-11-22 23:52:40 +08:00
192968bac8 Improve permission.State 2023-11-22 00:03:33 +08:00
23d4488b64 feat: fix login page path after logout (#2493)
Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-11-21 23:37:35 +08:00
23f4684e1d feat: make MFA works for CAS login (#2506)
* feat: make MFA works for CAS login

* fix: Reduced code redundancy

* fix: Modified the format of the code.

* fix: fix an error with the 'res' variable

* Update LoginPage.js

* Update LoginPage.js

* Update LoginPage.js

* Update MfaAuthVerifyForm.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-11-21 21:35:19 +08:00
1a91e7b0f9 feat: support LDAP in Linux (#2508) 2023-11-21 14:01:27 +08:00
811999b6cc feat: fix error handling in CheckPassword() related functions 2023-11-20 21:49:19 +08:00
7786018051 feat: use short state for OAuth provider (#2504)
* fix: use fixed length of state

* fix: use short state
2023-11-19 07:30:29 +08:00
6c72f86d03 fix: support LDAP in linux (#2500)
Co-authored-by: Xiang Zhen Gan <m1353825@163.com>
2023-11-16 23:58:09 +08:00
5b151f4ec4 feat: improve cert edit page UI 2023-11-13 15:57:46 +08:00
e9b7d1266f Fix API typo: /get-global-certs 2023-11-13 14:22:40 +08:00
2d4998228c Add organization.MasterVerificationCode 2023-11-13 13:53:41 +08:00
d3ed6c348b Improve GetOAuthToken() API's parameter handling 2023-11-13 02:30:32 +08:00
a22e05dcc1 feat: fix the UI and navigation errors on the prompt page (#2486) 2023-11-12 15:54:38 +08:00
0ac2b69f5a feat: support WeChat Pay via JSAPI (#2488)
* feat: support wechat jsapi payment

* feat: add log

* feat: update sign

* feat: process wechat pay result

* feat: process wechat pay result

* feat: save wechat openid for different app

* feat: save wechat openid for different app

* feat: add SetUserOAuthProperties for signup

* feat: fix openid for wechat

* feat: get user extra property in buyproduct

* feat: remove log

* feat: remove log

* feat: gofumpt code

* feat: change lr->crlf

* feat: change crlf->lf

* feat: improve code
2023-11-11 17:16:57 +08:00
d090e9c860 Improve downloadImage() 2023-11-10 08:35:21 +08:00
8ebb158765 feat: improve README 2023-11-09 21:52:52 +08:00
ea2f053630 feat: add fields like Email to user profile in JWT-Empty mode 2023-11-09 20:20:42 +08:00
988b14c6b5 Fix user's UpdatedTime in other APIs 2023-11-08 20:22:28 +08:00
a9e72ac3cb feat: fix bug in GetAllowedApplications() 2023-11-08 10:31:24 +08:00
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
80b0d26813 fix: synchronize update the syncers (#2201)
Signed-off-by: baihhh <2542274498@qq.com>
2023-08-13 22:30:57 +08:00
db4ac60bb6 feat: fix LDAP mobile field incorrect mapped (#2206) 2023-08-12 13:45:26 +08:00
33a922f026 Add custom HTTP SMS provider 2023-08-12 12:52:53 +08:00
9f65053d04 Improve i18n 2023-08-12 02:44:38 +08:00
be969e5efa Fix typo 2023-08-11 22:18:35 +08:00
9156bd426b ci: Show provider.displayName in signin button 2023-08-11 16:29:52 +08:00
fe4a4328aa feat: refactor code in InitApi() 2023-08-11 16:17:29 +08:00
9899022bcd fix: check enforcer should not be nil (#2199)
* fix: check enforcer should not be nil

* fix: check enforcer should not be nil

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-11 12:31:49 +08:00
1a9d02be46 feat: use the casbin model to store relationships between users and groups (#2178)
* fix:reslove conflict

* fix: remove interface
2023-08-11 10:59:18 +08:00
eafaa135b4 Change builtInAvailableField back to 5 2023-08-11 02:45:11 +08:00
6746551447 Improve error message in InitEnforcer() 2023-08-11 02:36:29 +08:00
3cb46c3628 Add isKey to syncer's table 2023-08-09 00:33:04 +08:00
558bcf95d6 feat: save policy in adapter edit page (#2190)
* fix: save policy in adapter

* fix: disable edit for builtin adapter
2023-08-09 00:12:53 +08:00
bb937c30c1 Fix empty cert in getPaymentProvider() 2023-08-08 22:37:48 +08:00
8dfdf7f767 ci: add GoogleCloud and QiNiu in Storage (#2188)
* feat: add GoogleCloud and QiNiu in Storage

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

* Update qiniu_cloud.go

* Update storage.go

---------

Signed-off-by: baihhh <2542274498@qq.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-08 22:34:55 +08:00
62b2082e82 Add getUserOrganization() to user edit page 2023-08-08 21:58:27 +08:00
a1806439f8 Add UserPrincipalName and MemberOf to get-ldap-users API 2023-08-08 20:18:47 +08:00
01e58158b7 feat: Remove useless code 2023-08-08 19:16:55 +08:00
15427ad9d6 fix: fix add provider error (#2184) 2023-08-07 17:22:32 +08:00
d058f78dc6 fix: fix broken links (#2181) 2023-08-07 01:02:03 +08:00
fd9dbf8251 feat: add multiple SMS providers (#2182)
* feat: add amazon sns and azure acs provider

* feat: add msg91 sms provider

* feat: add infobip sms provider

* feat: add ucloud sms provider

* feat: add baidu cloud sms provider

* fix: fix logo and azure acs
2023-08-07 00:59:17 +08:00
3220a04fa9 fix: use org/groupName replace groupName (#2180) 2023-08-06 20:16:44 +08:00
f06a4990bd fix: rename in init.go (#2179)
* fix: rename in init.go

* fix: remove blank line

* fix: remove blank line

* Update init.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-08-06 13:07:30 +08:00
9df7de5f27 Improve menu icons 2023-08-05 18:00:24 +08:00
56c808c091 Improve menu 2023-08-05 17:41:35 +08:00
9fd2421564 Update @ant-design/cssinjs dependency to avoid build error 2023-08-04 01:22:57 +08:00
689d45c7fa feat: fix org name cannot be changed bug 2023-08-03 18:48:37 +08:00
c24343bd53 Fix XxxChangeTrigger() doesn't return error bug 2023-08-03 18:45:49 +08:00
979f43638d Change builtInAvailableField to 10 2023-08-03 18:17:15 +08:00
685a4514cd fix: revert adapter port vartype to int (#2174) 2023-08-03 09:35:16 +08:00
a05ca3af24 feat: use role ID to search in GetPermissionsAndRolesByUser() (#2170) 2023-08-02 20:58:06 +08:00
c6f301ff9e Support svg in downloadImage() 2023-07-31 20:23:28 +08:00
d7b2bcf288 feat: support payment cancel state (#2165) 2023-07-31 15:24:13 +08:00
67ac3d6d21 Fix typo 2023-07-31 15:23:44 +08:00
912d5c6a7f fix: support enforcerId parameter in Enforce API (#2164) 2023-07-31 00:20:53 +08:00
32fbb5b534 Support custom provider for storage API 2023-07-30 23:19:45 +08:00
21004f3009 Fix GetResources() missing items bug 2023-07-30 22:47:14 +08:00
463bacd53b Add GetDirectResources() 2023-07-30 22:01:10 +08:00
78dc660041 feat: support 3 more language (#2163)
Signed-off-by: baihhh <2542274498@qq.com>
2023-07-30 20:45:47 +08:00
2fb9674171 Fix file not exist panic in StaticFilter() 2023-07-30 19:03:21 +08:00
55c522d3b7 Improve provider type input box 2023-07-30 17:31:36 +08:00
f879170663 Remove AI related code 2023-07-30 14:39:27 +08:00
12e5d9b583 Remove adapter.file 2023-07-30 12:08:05 +08:00
eefa1e6df4 fix: fix paypal payment provider and refactor payment code (#2159)
* feat: support paypal payment provider

* feat: support paypal flow

* feat: use owner replace org for payment

* feat: update paypal logic

* feat: gofumpt

* feat: update payment

* fix: fix notify

* feat: delete log
2023-07-30 11:54:42 +08:00
026fb207b3 fix: remove model in adapter page (#2161) 2023-07-29 23:42:08 +08:00
ea10f8e615 feat: make hard-coded authz adapter editable, rename adapter to ormer (#2149)
* refactor: rename casbinAdapter to casdoorAdapter

* feat: add initEnforcer

* fix: router

* refactor: make hard-coded code configurable

* fix: data type

* feat: support sqlite3

* feat: disable delete and edit name for built in resources

* feat: optimize code

* fix: init

* fix: e2e

* fix: remove datasourcename

* fix: revert rename

* refactor: change all ORM's Adatper to Ormer

* refactor: name
2023-07-29 15:07:04 +08:00
74b058aa3f Fix sync-ldap-users() bug, brought by: 666ff48837 2023-07-29 13:14:55 +08:00
6c628d7893 Fix static path not changed bug in makeGzipResponse() 2023-07-29 12:23:48 +08:00
a38896e4d8 Improve swagger docs 2023-07-29 11:35:03 +08:00
5f054c4989 Fix product links 2023-07-28 15:08:45 +08:00
fb16d8cee6 fix: not set count of enforcers to the response (#2155) 2023-07-28 14:46:11 +08:00
5e4ba4f338 feat: add authorize button and defaultValue (#2152)
Signed-off-by: baihhh <2542274498@qq.com>
2023-07-27 23:55:35 +08:00
ca47af2ee1 Make post_logout_redirect_uri optional for logout 2023-07-27 23:26:30 +08:00
59da104463 fix: update ldap admin pwd only if changed (#2146)
* fix ldap pwd update

* fix: linter

* fix: simplify check
2023-07-27 17:49:15 +08:00
c5bb916651 fix: fix response data in PricingPage.js (#2143) 2023-07-27 10:46:31 +08:00
e98264f957 fix: application fails to call /api/get-resources (#2139)
just like other apis, resource.go.GetResources() no longer calls ApiController.RequireSignedInUser() to auth or check
2023-07-26 17:19:00 +08:00
6a952952a8 fix: unmask application for org admin (#2138)
* feat: unmask application with user admin

* Update application.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-26 17:17:49 +08:00
ba8a0f36be Support custom actions in permission edit page 2023-07-26 14:49:45 +08:00
b5e9084e5d feat: en/decodeURI in permission/role name (#2137) 2023-07-26 13:08:35 +08:00
55d5ae10f2 fix: fix infinite loop in containsRole() (#2136) 2023-07-25 20:53:08 +08:00
6986dad295 Use arg to control createDatabaseForPostgres() 2023-07-25 18:36:15 +08:00
949feb18af feat: add basic enforcer manager (#2130)
* feat: add basic enforcer manager

* chore: generate swagger
2023-07-25 17:17:59 +08:00
d1f88ca9b8 feat: support google one tap signin (#2131)
* feat: add google one tap support

* feat: gofumpt

* feat: add google provider rule conf

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

* feat: add cas login i18n

* feat: add CheckCasService for all cas api

* feat: gofumpt

* feat: replace 404

* feat: reuse i18n

* feat: delete CheckCasService

* Update token_cas.go

* Update LoginPage.js

* Update token_cas.go

---------

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

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

* Update index.css

---------

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

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

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

* revert handle error

* revert handle error
2023-07-23 09:49:16 +08:00
fc9528be43 Add createDatabaseForPostgres() 2023-07-22 16:19:13 +08:00
58e8f9f90b feat: fix Effect in Casbin rule (#2103)
* fix: Add `Effect` to Casbin rule of role

fix: https://github.com/casdoor/casdoor/issues/2102

* Update permission_enforcer.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-21 18:01:37 +08:00
e850e33f37 Fix error message of missing cert when login 2023-07-20 19:45:22 +08:00
d7110ff8bf feat: support MetaMask provider (#2084)
* feat: add metamask provider

* feat: add eth login

* feat: check eth sign

* feat: finish metamask signin/signup

* feat: support MetaMask provider link/unlink

* feat: update web/craco.config.js to handle polyfill

* feat: gofumpt idp/metamask.go

* feat: update MetaMask logo path

* feat: support MetaMask avatar
2023-07-20 17:51:36 +08:00
f923a8f0d7 fix: provide detailed description of ldap in swagger (#2094)
* provide detailed description of ldap in swagger

* modify the directory of swagger

fix: provide detailed description of ldap in swagger
2023-07-20 12:32:48 +08:00
7bfb74ba18 Fix typo 2023-07-19 19:34:43 +08:00
38f031bc86 Show access secret if isAdminOrSelf is true in get-user and get-account APIs 2023-07-19 19:14:53 +08:00
5c441d195c Add Effect to Casbin rule of add-permission 2023-07-19 18:52:22 +08:00
0639564d27 fix: check group name cannot be same as organization name (#2090) 2023-07-19 11:37:28 +08:00
6c647818ca feat: add "Sender number" input for Twilio SMS provider 2023-07-18 22:46:56 +08:00
8bc73d17aa feat: fix bug that themeEditor can not load saved theme data (#2085) 2023-07-17 22:57:55 +08:00
1f37c80177 feat: refactor code to add getStorageProvider() 2023-07-17 15:59:37 +08:00
7924fca403 fix: hidden bug of "like" query (#2082) 2023-07-16 17:11:32 +08:00
bd06996bab Improve CorsFilter for login API 2023-07-15 19:29:48 +08:00
19ab168b12 Fix panic in func (c *ApiController) GetUser() if no user exists in DB 2023-07-14 20:57:59 +08:00
854a74b73e feat: fix the error when user uploads avatar to minio (https) (#2078)
* fix: Error reported when user uploads avatar to minio (https)

* Update provider.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-14 15:58:30 +08:00
beefb0b432 fix: fix event-stream streaming output in prod mode (#2076) 2023-07-14 11:59:26 +08:00
d8969e6652 Support EnableSigninSession after SAML login 2023-07-14 11:27:18 +08:00
666ff48837 Use id param in /sync-ldap-users API 2023-07-13 00:14:18 +08:00
0a0c1b4788 Fix "Groups is immutable" bug when updating a user 2023-07-13 00:03:18 +08:00
438c999e11 Add password mask to /get-ldaps and /get-ldap APIs 2023-07-12 23:21:47 +08:00
a193ceb33d Fix bug in TestDeployStaticFiles() 2023-07-12 23:11:02 +08:00
caec1d1bac Only consider x509 certs in /.well-known/jwks API 2023-07-12 22:39:39 +08:00
0d48da24dc feat: fix wrong rowKey for tables (#2070) 2023-07-12 21:12:36 +08:00
de9eeaa1ef fix: init groups modify rule with admin (#2054) 2023-07-11 09:49:49 +08:00
ae6e35ee73 feat: fix bug that the password input disappears in login window (#2051)
Signed-off-by: baihhh <2542274498@qq.com>
2023-07-08 23:46:31 +08:00
a58df645bf fix: fix state after mfa is enabled (#2050) 2023-07-08 22:35:31 +08:00
68417a2d7a fix: /api/upload-resource panics when parsing file_type (#2046) 2023-07-07 16:18:25 +08:00
9511fae9d9 docs: add swagger docs for Resource-API (#2044)
swagger files are all auto generated.
2023-07-07 14:28:10 +08:00
347d3d2b53 feat: fix bugs in MFA (#2033)
* fix: prompt mfa binding

* fix: clean session when leave promptpage

* fix: css

* fix: force enable mfa

* fix: add prompt rule

* fix: refactor directory structure

* fix: prompt notification

* fix: fix some bug and clean code

* fix: rebase

* fix: improve notification

* fix: i18n

* fix: router

* fix: prompt

* fix: remove localStorage
2023-07-07 12:30:07 +08:00
6edfc08b28 Refactor the code 2023-07-07 00:13:05 +08:00
bc1c4d32f0 feat: user can upload ID card info (#2040)
* feat:user can upload ID card(#1999)

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

* feat: user can upload ID card, add diff languages

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

---------

Signed-off-by: baihhh <2542274498@qq.com>
2023-07-06 20:36:32 +08:00
96250aa70a docs: replace gitter links with discord (#2041) 2023-07-06 18:16:16 +08:00
3d4ca1adb1 feat: support custom user mapping (#2029)
* feat: support custom user mapping

* fix: parse id to string

* Update data.json

* Update data.json

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-07-05 20:35:02 +08:00
ba97458edd feat: fix StaticFilter issue 2023-07-05 17:54:39 +08:00
855259c6e7 feat: improve getOriginFromHost() for local machine name 2023-07-05 09:51:08 +08:00
28297e06f7 feat: IntrospectToken return the right Jti (JWT ID instead of User Id) (#2035) 2023-07-03 19:01:06 +08:00
f3aed0b6a8 Fix null panic in GetOrganizationByUser() 2023-07-03 14:56:14 +08:00
35e1f8538e feat: fix panic when url.Parse() fails to parse URL (#2034) 2023-07-03 12:35:22 +08:00
30a14ff54a Fix null issue in getDefaultApplication() 2023-07-02 09:44:48 +08:00
1ab7a54133 Add DefaultApplication to conf 2023-07-02 09:15:22 +08:00
0e2dad35f3 Improve OrganizationSelect width 2023-06-30 02:04:44 +08:00
d31077a510 Remove conf values 2023-06-30 01:38:48 +08:00
eee9b8b9fe feat: add organization context select box for admin (#2013)
* feat: organization as context

* feat: organization as context with backend filtration

* Update app.conf

* update app.conf and hide organization select for mobile.

---------

Co-authored-by: dplynsky <dplynsky@ptsecurity.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-30 01:32:34 +08:00
91cb5f393a fix: fix Swagger docs page (#2025)
Signed-off-by: baihhh <2542274498@qq.com>
2023-06-30 00:48:39 +08:00
807aea5ec7 feat: add tags to application (#2027)
* feat: add tags to application

* fix: fix for merge master

* feat: update i18n(backend&frontend) for application tags
2023-06-30 00:04:12 +08:00
1c42b6e395 fix: refactor the idp and regex code (#2030)
* refactor: validate util and idp

* chore: clean code

* chore: clean code
2023-06-29 21:44:14 +08:00
49a73f8138 fix: getOrganization without pagination for global admin (#2028)
* fix: getOrganization without pagination for global admin return only built-in org

* fix gofumpt
2023-06-29 18:56:19 +08:00
55784c68a3 Fix bug in /get-organizations API for org admin 2023-06-28 09:19:39 +08:00
8080b10b3b feat: show code signin page with password disabled (#2021) 2023-06-28 00:38:48 +08:00
cd7589775c feat: replace all panic by response err (#1993)
* fix: missing return after response error

* feat: handle error in frontend

* feat: disable loading and catch org edit error

* chore: i18 for error message

* chore: remove break line

* feat: application catching error
2023-06-27 21:33:47 +08:00
0a8c2a35fe feat: add TOTP multi-factor authentication (#2014)
* feat: add totp multi-factor authentication

* feat: add license

* feat:i18n and update yarn.lock

* feat:i18n

* fix: i18n
2023-06-24 18:39:54 +08:00
d1e734e4ce fix: set the default value of user.Groups for syncer (#2016)
fix: set the default value of user.Groups for syncer
2023-06-24 18:29:50 +08:00
68f032b54d fix: add isReadOnly for syncer (#2015)
* feat: add read only mod for syncer

* feat: change readOnlyEnable to isReadOnly
2023-06-24 17:56:41 +08:00
1780620ef4 feat: handle error when permission not found (#2012) 2023-06-24 00:30:43 +08:00
5c968ed1ce Fix avatar cannot show issue 2023-06-23 15:53:41 +08:00
4016fc0f65 Add EnableChatPages to Conf 2023-06-23 11:35:34 +08:00
463b3ad976 fix: refactor and optimize Enforce() API (#2009) 2023-06-22 17:45:24 +08:00
b817a55f9f Fix error handling in SetPassword() 2023-06-22 14:51:56 +08:00
2c2ddfbb92 feat: optimize batch-enforce (#1997) 2023-06-22 14:40:09 +08:00
cadb533595 fix: unsafe verification username in CheckUsername (#2006)
* Customization of the initialization file

* Unsafe verification username in CheckUsername
2023-06-21 23:20:23 +08:00
a3b0f1fc74 feat: add owner to getUserByWechatId() 2023-06-21 21:29:53 +08:00
c391af4552 feat: improve MFA by using user's own Email and Phone (#2002)
* refactor: mfa

* fix: clean code

* fix: clean code

* fix: fix crash and improve robot
2023-06-21 18:56:37 +08:00
6ebca6dbe7 fix: Gosec/sec fixes (#2004)
* Customization of the initialization file

* fix: G601 (CWE-118): Implicit memory aliasing in for loop

* fix: G304 (CWE-22): Potential file inclusion via variable

* fix: G110 (CWE-409): Potential DoS vulnerability via decompression bomb
2023-06-21 18:55:20 +08:00
d505a4bf2d Remove org API calls in PasswordModal page 2023-06-21 00:49:03 +08:00
812bc5f6b2 Fix "nu" bug in GetLanguage() 2023-06-20 21:16:01 +08:00
f6f4d44444 feat: remove url.JoinPath() to be compatible with Go 1.17 (#1995) 2023-06-20 17:44:40 +08:00
926e73ed1b fix: fix "Accept-Language" parsing in request (#1996) 2023-06-20 17:43:48 +08:00
65716af89e feat: deprecate the user group relation table (#1990)
* fix: deprecate the user group relation table

* fix: clean code

* fix: fix trigger

* Update group.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-19 19:08:45 +08:00
d9c4f401e3 Fix error in downloadImage() 2023-06-19 17:52:01 +08:00
58aa7dba6a Fix groups in GetUserInfo() 2023-06-19 11:06:55 +08:00
29fc820578 Set User.groups to [] 2023-06-19 09:42:17 +08:00
d0ac265c91 fix: Deprecate the id field in group (#1987) 2023-06-18 23:33:13 +08:00
3562c36817 feat: Revert "fix: fix URL path in MinIO storage provider" (#1988)
This reverts commit 3699177837.
2023-06-18 23:08:40 +08:00
7884e10ca3 Refactor adapter's owner and organization 2023-06-18 00:22:12 +08:00
12dee8afd3 Fix null options in checkPasswordComplexity() 2023-06-17 22:38:02 +08:00
ac4b870309 Improve getFaviconFileBuffer() 2023-06-17 12:50:01 +08:00
b9140e2d5a Refactor refreshAvatar() 2023-06-17 11:43:46 +08:00
501f0dc74f Add user_avatar.go 2023-06-17 01:25:15 +08:00
a932b76fba Remove useless check in SetPassword() 2023-06-17 00:58:31 +08:00
0f57ac297b ci: add password complexity options to organization edit page (#1949)
* Support uploading roles and permissions via xlsx file.

* Template xlsx file for uploading users and permissions.

* reformat according to gofumpt.

* fix typo.

* add password complexity options to organization edit page.

* add password complexity options to organization edit page.

* Fixed Typos.

* Fixed Typos.

* feat:add password complexity options to organization edit page

* Auto generate i18n fields.

* Refactor code according to instructions

* Support autocheck passwd complexity in frontend when setting passwd in user edit page.

* feat:Backend Support for password validation in signup and forget page.

* feat:Frontend Support for password validation in signup and forget page.

* Add default password complex option & Update historical empty filed with default option.

* Migrator for field `password_complex_options` in org table.

* feat: support frontend password complex option check in user_edit/forget/signup page.

* frontend update for user edit page

* update i18n file

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-17 00:07:36 +08:00
edc6aa0d50 feat: get all role/permission of an user (#1978) 2023-06-16 22:44:21 +08:00
ebc0e0f2c9 Update i18n words 2023-06-16 22:06:54 +08:00
63dd2e781e Update backend i18n files 2023-06-16 21:55:08 +08:00
b01ba792bb Rename to accessSecret 2023-06-16 20:42:15 +08:00
98fb9f25b0 feat: fix bug that users in role don't work for permissions (#1977)
* feat: fix check login permission

* feat: fix check login permission
2023-06-16 20:14:27 +08:00
cc456f265f feat: fix LDAP user password checking logic in GetOAuthToken() (#1975) 2023-06-15 21:04:09 +08:00
7058a34f87 feat: complete group tree (#1967)
* feat: complete group tree

* feat: ui

* fix: i18n

* refactor code

* fix: support remove user from group

* fix: format code

* Update organization.go

* Update organization.go

* Update user_group.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-14 23:27:46 +08:00
8e6755845f ci: fix bug in PaypalPaymentProvider (#1972) 2023-06-13 23:33:03 +08:00
967fa4be68 feat: add access key and secret key for user (#1971) 2023-06-13 22:18:17 +08:00
805cf20d04 feat: fix incorrect VerifyTypePhone value (#1968) 2023-06-13 17:26:37 +08:00
2a8001f490 fix: clean timeout when componentWillUnmount in PaymentResult page (#1962) 2023-06-13 02:00:52 +08:00
451fc9034f fix: fix bug in PayPal payment provider (#1959) 2023-06-12 13:43:37 +08:00
0e14a2597e feat: Add tree structure to organization page (#1910)
* rebase master

* feat: add group in userEditPage

* feat: use id as the pk

* feat: add groups item in user

* feat: add tree component

* rebase

* feat: ui

* fix: fix some bug

* fix: route

* fix: ui

* fix: improve ui
2023-06-12 09:27:16 +08:00
ff87c4ea33 feat: fix createDatabase arg not recognized bug 2023-06-12 01:57:58 +08:00
4f5396c70e Check error for CreateDatabase() 2023-06-12 01:47:26 +08:00
3c30222fce Fix payment owner issue 2023-06-12 00:34:41 +08:00
2d04731622 Provide default value for logConfig 2023-06-10 15:59:56 +08:00
e0d2bc3dc9 Return error in GetProviderFromContext() 2023-06-10 15:51:26 +08:00
0bda29f143 feat: show 404 error for non-existent objects in edit pages 2023-06-10 01:56:15 +08:00
05703720c5 Add Custom to resourceType 2023-06-09 21:52:30 +08:00
cc566bf31f Move DoMigration() after CreateTables() 2023-06-09 09:36:20 +08:00
e93d8c19d9 feat: resolve user pages malfunction after using tableNamePrefix (#1945) 2023-06-08 00:43:05 +08:00
f2e3182a69 Fix null value in backend Translate() 2023-06-07 02:17:48 +08:00
f934531083 Fix organization search in some pages 2023-06-06 20:53:45 +08:00
e1c0af345f feat: resolve casdoor as SAML SP with keycloak login not work bug (#1937)
* fix: resolve casdoor as SAML SP with keycloak login not work

* Update provider.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-06 15:19:00 +08:00
3b3bfe39f9 Fix user field bug 2023-06-06 14:59:50 +08:00
18cc952f8e feat: Customization of the initialization file (#1936) 2023-06-05 21:00:28 +08:00
43439bc8c6 Apply tableNamePrefix before migration 2023-06-05 00:30:48 +08:00
9a2800e3b3 Add error to Enforce() 2023-06-04 17:29:34 +08:00
fdaad2b608 chore: refactor enforce() handler and update Swagger docs (#1931)
* chore: add swaggerof enforce api

* Update enforcer.go

* Update string.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-06-04 17:19:58 +08:00
2d43fe0b39 Fix cert empty issue in GetSamlMeta() 2023-06-04 01:25:18 +08:00
5d776a3ce6 fix: handle error of list in frontend (#1930) 2023-06-04 01:21:24 +08:00
5ec7a54bf8 Add description fields to objects 2023-06-04 01:21:17 +08:00
0c118477e8 Add groups to UserInfo 2023-06-04 01:21:10 +08:00
c858d0e0b0 Fix model page bug 2023-06-03 10:35:58 +08:00
9cffb43265 Fix subscription page bugs 2023-06-03 10:15:29 +08:00
51a76518ad Init adapter in getEnabledSyncerForOrganization() 2023-06-03 09:23:36 +08:00
08dbbab70e feat: revert "feat: fix the bug that sycner does not initialize" (#1926)
This reverts commit ec3c24ba68.
2023-06-03 09:17:34 +08:00
0ec22ae6ff Fix null bug in getLanguage() 2023-06-03 00:29:08 +08:00
ec3c24ba68 feat: fix the bug that sycner does not initialize (#1924) 2023-06-03 00:15:28 +08:00
ed688efdbb Fix bug in org user list page 2023-06-02 22:09:18 +08:00
06543a01d3 Add organization to /userinfo 2023-06-02 21:51:05 +08:00
70c372c3f7 Fix Provider API responses 2023-06-02 11:49:38 +08:00
b1b3184e75 Speed up user pagination query 2023-06-01 22:55:44 +08:00
5349fa7ff3 Speed up object.DoMigration() 2023-06-01 22:25:19 +08:00
9147225956 feat: fix table sticky columns on chat and message pages (#1917) 2023-06-01 21:02:21 +08:00
11f3af1ede Improve Select modes 2023-05-31 17:36:11 +08:00
0aa4df40c6 Fix i18n 2023-05-31 11:46:03 +08:00
7caa885131 Fix subscription bugs 2023-05-31 11:33:01 +08:00
f4b69cad9b Add owner to select-plan page 2023-05-31 00:29:54 +08:00
fb1db7823b Add DummyPaymentProvider 2023-05-30 23:25:58 +08:00
10e66f8020 fix: Get logger configuration from app.conf file (#1907)
* feat: Get logger configuration from file

* feat: Get logger configuration from file

* Remove GetConfigLogs()
2023-05-30 21:30:09 +08:00
4c8648d323 Add PaypalPaymentProvider 2023-05-30 20:32:05 +08:00
02e692a300 feat: return most backend API errors to frontend (#1836)
* feat: return most backend API errros to frontend

Signed-off-by: yehong <239859435@qq.com>

* refactor: reduce int type change

Signed-off-by: yehong <239859435@qq.com>

* feat: return err backend in token.go

Signed-off-by: yehong <239859435@qq.com>

---------

Signed-off-by: yehong <239859435@qq.com>
2023-05-30 15:49:39 +08:00
34151c0095 feat: Support uploading roles and permssions via xlsx files. (#1899)
* Support uploading roles and permissions via xlsx file.

* Template xlsx file for uploading users and permissions.

* reformat according to gofumpt.

* fix typo.
2023-05-28 11:29:43 +08:00
c7cea331e2 Improve NewWechatPaymentProvider() arg 2023-05-27 19:28:24 +08:00
8ede4993af feat: specify login organization 2023-05-27 19:02:54 +08:00
d04dd33d8b refactor: New Crowdin Backend translations by Github Action 2023-05-27 09:52:47 +00:00
8cb21253f6 refactor: New Crowdin translations by Github Action 2023-05-27 09:52:19 +00:00
7fc697b711 ci: fix bug in WeChat payment provider 2023-05-27 17:50:56 +08:00
80e6e7f0a7 fix: fix bug about updating parent component value in CountryCodeSelect (#1891) 2023-05-25 10:45:13 +08:00
d29fc88d68 Add getRawGetParameter() 2023-05-25 09:47:39 +08:00
225e9cf70a fix: set initial value in CountryCodeSelect (#1890) 2023-05-24 23:27:04 +08:00
c57c6e37dd Fix bug in getRedirectUri() 2023-05-24 23:22:25 +08:00
4d860525bf feat: fix MFA page bug in OAuth login (#1889) 2023-05-24 21:31:03 +08:00
a64263f812 Support "#" in redirectUri 2023-05-24 21:29:45 +08:00
95ab2472ce Make logo length to 200 2023-05-23 21:57:35 +08:00
54e4747dbc refactor: code-optimization (#1885)
* refactor: code-optimization

* fix: restoring code style

* fix: gofmt
2023-05-23 17:54:51 +08:00
2389d47c34 Fix getFormattedDate() 2023-05-23 15:09:53 +08:00
9c4f0f042e fix: update Go dependencies (#1880)
* Vulnerability fix : CVE-2021-30080  CVE-2021-39391 CVE-2022-41723 CVE-2022-21698  CVE-2023-26483 etc.

* fix: CVE-2021-30080  CVE-2021-39391 CVE-2022-41723 CVE-2022-21698  CVE-2023-26483 etc.
2023-05-23 14:43:18 +08:00
e25e210b06 Improve upload resource page 2023-05-23 10:07:59 +08:00
df61a536c1 feat: add gzip support for static filter (#1875)
* feat: add gzip support for static filter

* Update static_filter.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-22 22:40:46 +08:00
47da3cdaa0 fix: resolve get resource list problem (#1877) 2023-05-22 22:35:12 +08:00
8d246f2d98 ci: revert "feat: fix UI in IE11" (#1879)
This reverts commit 44cd55e55f.
2023-05-22 22:21:56 +08:00
44cd55e55f feat: fix UI in IE11 (#1878) 2023-05-22 16:59:37 +08:00
6b42d35223 Fix state encoding for Moodle 2023-05-21 15:47:18 +08:00
c84150cede Fix getObject() bug for some API 2023-05-21 11:07:01 +08:00
de2689ac39 fix: revert "feat: fix UI in IE11" (#1873)
* Revert "feat: fix UI in IE11 (#1871)"

This reverts commit 319031da28.

* Update MfaVerifyForm.js
2023-05-21 00:43:42 +08:00
88c0856d17 feat: add subscription managment (#1858)
* feat: subscription managment

* fix: remove console log

* fix: webhooks

* fix linter

* fix: fix via gofumpt

* fix: review changes

* fix: Copyright 2023

* Update account.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-20 15:56:21 +08:00
319031da28 feat: fix UI in IE11 (#1871) 2023-05-19 21:47:02 +08:00
d20f3eb039 feat: support get user by userId and owner (#1870)
* feat: support get user by userId and owner

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-19 21:46:44 +08:00
3e13e61d8f fix: sdk user is not Global Admin (#1868) 2023-05-19 21:24:55 +08:00
1260354b36 fix: add sAMAccountName for AD search (#1869) 2023-05-19 21:16:59 +08:00
af79fdedf2 feat: add new language: "pt" (#1837)
* feat: Added new locales pt-br

* fix: Changed pt-br to pt

* feat: Updated app.conf

* feat: Updated Setting.js

* feat: Changed folder locales pt-br to pt

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-19 16:57:44 +08:00
02333f2f0c Add "pt" language to backend 2023-05-19 16:42:31 +08:00
79bd58e0e6 Use util.GetId() 2023-05-19 14:26:32 +08:00
de73ff0e60 Add IsMaskedEnabled to provider API 2023-05-19 13:09:53 +08:00
a9d662f1bd Improve Migrator_1_314_0_PR_1841 speed 2023-05-19 02:55:36 +08:00
65dcbd2236 feat: compatible different uid of LDAP server (#1860)
* feat: compatible different uid of LDAP server

* Update organization.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-19 02:34:25 +08:00
6455734807 fix: fix incorrect LDAP sync status (#1859) 2023-05-18 22:03:53 +08:00
2eefeaffa7 feat: enforce by using resourceId (#1855)
* feat: enforce by using resourceId

* Update permission.go

* chore: fix cilint for enforcer.go

---------

Co-authored-by: tinhtt4 <tinhtt4@vng.com.vn>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-18 16:36:03 +08:00
04eaad1c80 Fix getCertByApplication() 2023-05-18 16:32:43 +08:00
9f084a0799 Can update user with OAuth values 2023-05-18 15:58:41 +08:00
293b9f1036 Remove languages in app.conf 2023-05-18 15:44:11 +08:00
437376c472 Fix CheckAccessPermission() 2023-05-18 13:36:16 +08:00
cc528c5d8c Add object to webhook 2023-05-17 23:57:14 +08:00
54e2055ffb Fix Beego filter: RecordMessage 2023-05-17 23:01:59 +08:00
983a30a2e0 Dingtalk now supports linking with corpMobile 2023-05-17 22:14:57 +08:00
37d0157d41 Fix application.EnableSignUp bug 2023-05-17 21:56:36 +08:00
d4dc236770 Fix refreshExpireInHours zero value issue 2023-05-17 20:47:59 +08:00
596742d782 Show org column better for admin (shared) 2023-05-17 17:30:47 +08:00
ce921c00cd fix: resolve the problem of cert being unable to be accessed properly (#1850)
* fix: resolve the problem of cert being unable to be accessed properly

* Update CertEditPage.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-17 17:17:58 +08:00
3830e443b0 Put webhook's RecordMessage() to FinishRouter stage 2023-05-17 16:32:12 +08:00
9092cad631 feat: support forced binding MFA after login (#1845) 2023-05-17 01:13:13 +08:00
0b5ecca5c8 Support empty application in page 2023-05-16 22:17:39 +08:00
3d9b305bbb Add /api/health API 2023-05-16 21:47:34 +08:00
0217e359e7 Update to Go 1.19.9 and Node 16.18.0 in Dockerfile 2023-05-16 20:33:31 +08:00
695a612e77 Improve passwordType in CheckPassword() 2023-05-16 20:14:05 +08:00
645d53e2c6 feat: User should have PasswordType like Organization (#1841)
* fixes #1840: [backend] User should have PasswordType like Organization is

* Update migrator.go

* Update and rename migrator_1_314_0_PR_1838.go to migrator_1_314_0_PR_1841.go

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-16 20:11:19 +08:00
73b9d73f64 Add CustomFooter to Conf.js 2023-05-15 16:49:45 +08:00
c6675ee4e6 feat: AI responses support streaming (#1826)
Is an AI response that supports streaming return
2023-05-13 11:31:20 +08:00
6f0b7f3f24 Support modelId arg in Enforce() API 2023-05-12 21:39:57 +08:00
776a682fae Improve args of Enforce() API 2023-05-12 21:32:48 +08:00
96a3db21a1 Support LDAP search by user tag 2023-05-12 13:03:43 +08:00
c33d537ac1 Add formCssMobile to application 2023-05-12 12:16:03 +08:00
5214d48486 Fix authorized issue of UploadResource() API 2023-05-12 01:00:06 +08:00
e360b06d12 Fix termsOfUse upload in application edit page 2023-05-10 23:57:03 +08:00
3c871c38df Fix message and chat owner bug 2023-05-10 22:32:32 +08:00
7df043fb15 fix: fix cypress error (#1817)
* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error

* fix: fix cypress error
2023-05-09 20:51:07 +08:00
cb542ae46a feat: fix org admin permissions (#1822) 2023-05-09 00:06:52 +08:00
3699177837 fix: fix URL path in MinIO storage provider(#1818) 2023-05-08 16:48:56 +08:00
3a6846b32c feat: fix bug that logging in with account/password cannot redirect successfully (When Casdoor working as a OAuth server) (#1819) 2023-05-08 16:37:56 +08:00
50586a9716 feat: improve determination about whether dest is mail or phone and mask props (#1814) 2023-05-07 21:19:51 +08:00
9201992140 Fix LDAP server bugs 2023-05-06 23:31:46 +08:00
eb39e9e044 feat: add multi-factor authentication (MFA) feature (#1800)
* feat: add two-factor authentication interface and api

* merge

* feat: add Two-factor authentication accountItem and two-factor api in frontend

* feat: add basic 2fa setup UI

* rebase

* feat: finish the two-factor authentication

* rebase

* feat: support recover code

* chore: fix eslint error

* feat: support multiple sms account

* fix: client application login

* fix: lint

* Update authz.go

* Update mfa.go

* fix: support phone

* fix: i18n

* fix: i18n

* fix: support preferred mfa methods

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-05-05 21:23:59 +08:00
5b27f939b8 Improve add model initialization 2023-05-05 01:51:34 +08:00
69ee6a6f7e Move result page into entry page 2023-05-05 01:08:56 +08:00
bf6d5e529b Add "from" to Email provider 2023-05-04 23:41:37 +08:00
55fd31f575 Disable built-in/admin's unexpected change 2023-05-04 22:12:57 +08:00
05c063ac24 Set email's SkipUsernameCheck to true 2023-05-04 00:29:12 +08:00
38da63e73c Improve answer text 2023-05-02 23:33:09 +08:00
cb13d693e6 Add getTokenSize() 2023-05-02 10:04:11 +08:00
d699774179 Improve i18n.Translate() 2023-05-02 01:30:32 +08:00
84a7fdcd07 Handle message answer 2023-05-02 01:30:06 +08:00
2cd6f9df8e Add /api/get-message-answer API 2023-05-01 23:15:51 +08:00
eea2e1d271 Add ai package 2023-05-01 17:19:45 +08:00
48c5bd942c Fix chat UI 2023-05-01 16:23:48 +08:00
d01d63d82a Improve chat menu height 2023-05-01 14:11:17 +08:00
e4fd9cca92 Fix new chat button 2023-05-01 13:27:49 +08:00
8d531b8880 Fix getStateFromQueryParams() crash when provider name is non-latin 2023-05-01 10:32:08 +08:00
b1589e11eb Fix signin preview when there's no redirectUris 2023-05-01 10:31:21 +08:00
b32a772a77 Add jobNumber to dingtalk provider 2023-04-29 21:48:52 +08:00
7e4562efe1 Change org.defaultAvatar to 200 length 2023-04-29 08:33:04 +08:00
3a6ab4cfc6 Support mobile in DingTalk userinfo 2023-04-29 01:24:45 +08:00
fba4801a41 feat: make redirectUri token param follow OAuth2 standard (#1796)
* fix: rename token to access_token in implicit flow; change ? in the redirect uri to &

* fix typo
2023-04-28 23:54:48 +08:00
da21c92815 feat: support sub role synced update (#1794) 2023-04-28 22:14:37 +08:00
66c15578b1 feat: fix the order of Method and Name in System Info (#1797)
* fix: fixed the order of Method and Name in System Info

* fix: add i18n for System Info
2023-04-28 22:11:10 +08:00
f272be67ab Improve i18n 2023-04-28 18:43:41 +08:00
e4c36d407f feat: fix prometheus filter bugs (#1792)
* fix: fix prometheus

* fix: count latency with prefix api

* fix: latency should not be counted when startTime is nil
2023-04-26 22:18:48 +08:00
4c1915b014 fix: make query with like more precise (#1791) 2023-04-26 18:21:13 +08:00
6c2b172aae feat: fix function CheckAccountItemModifyRule (#1789)
* feat: fix function CheckAccountItemModifyRule

* fix: admin changes its own username

* fix: current user changes its own username

* Update user.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-04-26 16:21:58 +08:00
95f4f4cb6d feat: refactor out form package and optimize verification code module (#1787)
* refactor: add forms package and optimize verification code module

* chore: add license

* chore: fix lint

* chore: fix lint

* chore: fix lint

* chore: swagger
2023-04-25 23:05:53 +08:00
511aefb706 Disable faulty Beego filter 2023-04-25 20:02:13 +08:00
1003639e5b feat: support for prometheus (#1784) 2023-04-25 16:06:09 +08:00
fe53e90d37 fix: signup page of the app-built-in failed to load (#1785) 2023-04-25 16:00:24 +08:00
8c73cb5395 fix: fix golangci-lint (#1775) 2023-04-23 17:02:29 +08:00
06ebc04032 Can add/delete chat 2023-04-23 01:19:44 +08:00
0ee98e2582 Add loading to chat box 2023-04-23 00:25:09 +08:00
d25508fa56 Improve chat UI 2023-04-22 23:20:40 +08:00
916a55b633 fix: fixed failed to update information when name duplicate (#1773)
* fix: fixed failed to update information when name duplicate

* fix: Use GetOwnerAndNameFromId and GetId functions instead of split

* Update organization.go

* Update role.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-04-22 21:15:06 +08:00
a6c7b95f97 fix: fixed rows duplicates after sort by column (#1772) 2023-04-22 20:18:38 +08:00
4f8dd771bc feat: fix bug that can not get application in signup/oauth/ router (#1766) 2023-04-22 18:20:45 +08:00
e0028f5eed fix: add more events to webhooks (#1771) 2023-04-22 17:11:28 +08:00
6d6cbc7e6f feat: add dynamic mode for provider to enable verification code when the login password is wrong (#1753)
* fix: update webAuthnBufferDecode to support Base64URL for WebAuthn updates

* feat: enable verification code when the login password is wrong

* fix: only enable captcha when login in password

* fix: disable login error limits when captcha on

* fix: pass "enableCaptcha" as an optional param

* fix: change enbleCapctah to optional bool param
2023-04-22 16:16:25 +08:00
ee8c2650c3 Remove useless "/api/login/oauth/code" API and update Swagger 2023-04-22 09:47:52 +08:00
f3ea39d20c Fix result page button link 2023-04-21 23:56:33 +08:00
e78d9e5d2b Fix local file system storage provider path error 2023-04-21 10:12:09 +08:00
19209718ea feat: fix wrong CAS login mode (#1762) 2023-04-20 22:18:02 +08:00
e75d26260a Fix table name in getEnforcer() 2023-04-20 01:33:47 +08:00
6572ab69ce fix: fix pemContent decode error bug for WeChat Pay provider (#1751) 2023-04-19 22:13:13 +08:00
8db87a7559 fix: function comments (#1757)
Modify the function annotation so that the swagger can parse correctly
2023-04-19 21:19:48 +08:00
0dcccfc19c feat: rollback anted to v5.2.3 (#1755) 2023-04-19 11:30:49 +08:00
96219442f5 feat: fix Tencent Cloud OSS storage connect incorrect issue (#1752)
* fix: fix Tencent Cloud OSS storage connect incorrect

* Update provider.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-04-18 21:30:46 +08:00
903745c540 fix: improve LDAP page UI (#1749)
* refactor: improve LDAP sync page

* refactor: update anted version

* chore: i18
2023-04-17 22:03:05 +08:00
df741805cd Fix chat send 2023-04-17 20:50:03 +08:00
ee5c3f3f39 feat: fix display name null error during 3rd-party binding (#1747) 2023-04-17 15:39:33 +08:00
714f69be7b Use HTTP for IP host in getOriginFromHost() 2023-04-17 00:55:40 +08:00
0d12972e92 Fix "auto single OAuth signin doesn't work" bug 2023-04-17 00:38:48 +08:00
78b62c28ab Fix the wrong order of g policy in enforce() API 2023-04-16 22:26:22 +08:00
wht
5c26335fd6 feat: add rule option for phone in application's signup page (#1745) 2023-04-16 20:34:06 +08:00
7edaeafea5 Call refreshAvatar() in addUser() 2023-04-16 01:00:02 +08:00
336f3f7a7b Add user.refreshAvatar() 2023-04-16 01:00:02 +08:00
47dc3715f9 feat: handle error when parsing samlResponse (#1744)
* fix: handle err from parse samlResponse

* fix: lint
2023-04-16 00:36:25 +08:00
7503e05a4a Improve menu style 2023-04-15 18:08:21 +08:00
b89cf1de07 Add karma to account items 2023-04-15 16:05:33 +08:00
be87078c25 Fix vi i18n 2023-04-15 14:16:49 +08:00
faf352acc5 Fix i18n 2023-04-15 11:17:31 +08:00
0db61dd658 Add empty list item and expand menu by default 2023-04-15 10:54:56 +08:00
ebe8ad8669 Improve UI effect 2023-04-15 10:54:56 +08:00
2e01f0d10e Add input box 2023-04-15 10:54:55 +08:00
754fa1e745 Add chat box 2023-04-15 10:54:55 +08:00
8b9e0ba96b Add chat page 2023-04-15 10:54:55 +08:00
b0656aca36 Add chat and message pages 2023-04-15 10:54:54 +08:00
623b4fee17 feat: pre-ensure tempFiles folder exists before uploading files (#1739)
When deployed with docker, the user `casdoor` has no permission to mkdir `tempFiles`, so let's create the folder first.
2023-04-14 19:14:59 +08:00
1b1de1dd01 feat: add LDAP custom filter support (#1719)
* refactor: improve ldap server code

* feat: custom filter

* fix: fix displayName mapping

* feat: add custom filter search fields

* chore: add license

* chore: i18n

* chore: i18n

* chore: update init field
2023-04-13 14:12:31 +08:00
968d8646b2 fix: update webAuthnBufferDecode to support Base64URL for WebAuthn updates (#1734) 2023-04-12 21:33:54 +08:00
94eef7dceb feat: fix adapter set organizations invalid bug (#1729) 2023-04-11 22:38:00 +08:00
fe647939ce fix: fix CAS callback url not match bug (#1728)
Co-authored-by: mfk <mfk@hengwei.com.cn>
2023-04-11 19:26:57 +08:00
984a69cb4b feat: fix wrong Vietnamese flag (#1724)
* fix wrong Vietnam country code

* fix wrong Vietnam country code

* fix wrong Vietnam country code

* fix wrong Vietnam country code
2023-04-10 22:42:12 +08:00
098a1ece68 fix: rollback the version of webauthn in go mod to fix "atob" bug (#1721) 2023-04-10 20:14:27 +08:00
ad6f2ad2e1 feat: add wechatpay support. (#1710)
* feat: add wechatpay support.

* feat: add wechatpay support.

* Update wechatv3pay.go

* fix: update format.

* Update wechatv3pay.go

* Update wechatv3pay.go

* Update wechatv3pay.go

* fix: update file format.

* fix: improve the front of wechat payment.

* fix: change clientId2 to clientId.

* fix: fix the code format.

* fix: return backend error information to frontend.
2023-04-10 18:04:10 +08:00
2d55252261 Add chat and message pages 2023-04-09 15:54:22 +08:00
30ea3a1335 Improve getTags() 2023-04-09 15:54:21 +08:00
b7d78d1e27 fix: validate parameter and nil in func updateUser (#1714)
* fix: validate parameter and nil in func updateUser

* fix: delete blank line
2023-04-09 10:35:30 +08:00
3d5a645a3b feat: fix field name error of termsOfUse (#1715) 2023-04-09 01:01:04 +08:00
4ad21e7781 fix: fix WeCom provider method 2023-04-07 01:10:46 +08:00
b99a0c3ca2 feat: optimize the "forget password" page (#1709) 2023-04-06 23:06:18 +08:00
e1842f6b80 feat: fix LDAP server handle filter without CN field as * (#1705)
* fix: set ldap server default filter name as *

* fix: default use built-in organization to bind

* chore: use cache reduce the ci test time
2023-04-04 20:51:28 +08:00
0781a3835d feat: improve i18n to have proper German translation in web/ (#1702) 2023-04-02 10:52:30 +08:00
98a99f0215 Fix bug in getMemoryUsage() 2023-04-02 10:50:41 +08:00
681b086de0 Fix session page highlight 2023-04-01 17:36:50 +08:00
cdcc0b39e2 feat: filter not selected provider item (#1701) 2023-04-01 10:22:18 +08:00
8eb68ba817 fix: fix AAD single-tenant mode bug 2023-03-31 19:24:03 +08:00
8d1ae4ea08 Fix organization page bug 2023-03-31 18:35:57 +08:00
9c8ea027ef feat: add the missing userId param docs for get-user API (#1698)
* Add roles to SAML response

* Fix: Add back missing get-user userId param doc.

Signed-off-by: zzjin <tczzjin@gmail.com>

* Update user.go

---------

Signed-off-by: zzjin <tczzjin@gmail.com>
Co-authored-by: Yang Luo <hsluoyz@qq.com>
2023-03-30 18:39:14 +08:00
aaa56d3354 Add roles to SAML response 2023-03-30 14:43:34 +08:00
b45c49d3a4 feat: fix incorrect preferred_username field mapping in OIDC (#1697) 2023-03-29 22:18:12 +08:00
5b3202cc89 feat: fix phone validation bug in signup page (#1693) 2023-03-27 22:52:49 +08:00
5280f872dc Speed up GetOAuthToken() 2023-03-27 14:05:44 +08:00
fd61b963d5 feat: [SAML + long button crash] fix Disabling "Enable password" leads to white app page when SAML provider is active (#1691)
* fix: saml long button crush

* fix: sue svg

* Update Setting.js

* Update LoginButton.js

* Update ProviderButton.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-03-26 23:56:43 +08:00
a8937d3046 feat: refactor agreement modal and create folders to classify components (#1686)
* refactor: refactor agreement modal and create folders to classify components

* fix: i18

* fix: i18

* fix: i18n
2023-03-26 18:44:47 +08:00
32b05047dc Update system info API swagger 2023-03-26 10:19:59 +08:00
117ee509cf feat: fix name format in application login: GetClientCredentialsToken() (#1639) 2023-03-25 23:02:08 +08:00
daf3d374b5 fix: adjust error result position (#1683) 2023-03-25 09:36:23 +08:00
337ee2faef feat: fix the bug that autoSignin generates two callback AJAX calls (#1682) 2023-03-24 23:17:54 +08:00
989fec72bf Add /api/user API for Flarum's FoF Passport plugin 2023-03-24 01:02:04 +08:00
76eb606335 Support AAD tenant auth URL 2023-03-23 22:37:53 +08:00
c6146a9149 feat: fix bug that login by saml provider can not find application (#1676) 2023-03-23 21:38:33 +08:00
f191488338 feat: support popup mode in OAuth (#1668)
* feat: support `popup` mode in OAuth

* feat: sendMessage when popup window closed

* fix: fix param name error
2023-03-22 00:15:17 +08:00
da7336a9a4 feat: support getting versionInfo in docker (#1673)
* feat: support getting versionInfo in docker

* fix: fix build

* fix: fix build

* fix: fix system
2023-03-21 20:46:17 +08:00
b3806070ac Finish Go i18n texts 2023-03-20 01:10:48 +08:00
c7b9a77b4a Remove Go i18n duplicates 2023-03-19 22:09:19 +08:00
4c4ad8320d feat: optimize getEnforcer, only load filtered polices (#1669) 2023-03-19 20:13:48 +08:00
89d29c2519 Add "empty" to i18n 2023-03-19 20:13:07 +08:00
98f962f818 Support i18n of id 2023-03-19 19:56:56 +08:00
5989c4ff34 Remove i18n duplicates 2023-03-19 01:03:12 +08:00
1de76e4da9 Finish i18n of vi 2023-03-19 00:13:16 +08:00
4e62c255b3 Finish i18n of ru 2023-03-18 23:05:37 +08:00
7ee54cb089 Finish i18n of ko 2023-03-18 22:24:06 +08:00
bea03635a1 Finish i18n of ja 2023-03-18 21:46:17 +08:00
2bc4cd9337 Fix i18n issue 2023-03-18 20:42:02 +08:00
ed9ceaefe1 Finish i18n of de 2023-03-18 20:31:31 +08:00
3dec2fdc18 Fix i18n issue 2023-03-18 19:05:58 +08:00
31e4813df9 Finish i18n of fr 2023-03-18 18:54:05 +08:00
263f804ab8 Finish i18n of es 2023-03-18 17:24:31 +08:00
d383de256b Refactor to parseAllWords() 2023-03-18 14:16:15 +08:00
28d24cc913 Improve i18n 2023-03-18 12:18:42 +08:00
bd5c706317 Fix i18n code 2023-03-18 00:17:07 +08:00
fba0021e22 Fix enableLinkWithEmail UI 2023-03-17 23:47:13 +08:00
aba17e2bc1 feat: revert PR: "fix: disable cookie for static files" (#1666)
This reverts commit 312412ffe4.
2023-03-17 17:27:12 +08:00
wht
dd939b5c7e feat: improve tooltips and i18n for frontend (#1645)
* feat: Add english tooltips

* fix: add untranslated tooltips
2023-03-16 14:53:58 +08:00
eeba21bf0d Support acme-challenge 2023-03-16 00:25:03 +08:00
5e47406e09 Enable CORS for "OPTIONS" request 2023-03-16 00:24:18 +08:00
fd883a3211 feat: improve verification error translation (#1660) 2023-03-15 23:44:38 +08:00
mos
312412ffe4 fix: disable cookie for static files (#1656)
Co-authored-by: chenjpu <bing.chen@hgcitech.com>
2023-03-15 22:09:10 +08:00
295a69c5f7 feat: support LDAP with SSL/TLS enabled (#1655) 2023-03-15 11:12:31 +08:00
a8a8f39963 feat: use GetUserCount to optimize login performance (#1653) 2023-03-14 14:38:39 +08:00
90f8eba02d feat: can send Aliyun test SMS now (#1651) 2023-03-13 17:48:58 +08:00
2cca1c9136 feat: refactor LDAP backend code and improve frontend operation (#1640)
* refactor: simplify ldap backend code and improve frontend operation

* chore: add skipCi tag in sync_test.go

* fix: ui
2023-03-12 11:12:51 +08:00
c2eebd61a1 Add TestStartSyncJob() 2023-03-12 05:38:39 +08:00
59566f61d7 Refactor sync code 2023-03-12 05:10:23 +08:00
7e4c9c91cd improve sending text 2023-03-10 22:35:47 +08:00
430ee616db fix user list shows all users bug 2023-03-10 21:59:57 +08:00
2e3a323528 feat: Dingtalk provider supports fetching organization email (#1636)
* feat(dingtalk): try to get email from corp app

* chore: format codes

* chore: format codes (#1)

* Delete .fleet directory

* fix: fix syntax errors

* Update dingtalk.go

* style: fmt codes with gofumpt

---------

Co-authored-by: aidenlu <aiden_lu@wochacha.com>
2023-03-10 21:47:54 +08:00
09e8408a3d Fix Popconfirm text 2023-03-10 19:17:53 +08:00
2998bbf4b9 fix: Put Popconfirm into a React component. (#1638)
* add "Sure to delete" to i18n(#1569)

* fix: add sure to delete to i18n

* fix: Put Popconfirm into a React component.
2023-03-10 19:16:08 +08:00
404382f2e0 feat: fix incompatibility css inset when Safari version <=14.1 (#1635) 2023-03-09 22:01:39 +08:00
71db1f62a9 Fix DingTalk oauth link 2023-03-09 21:11:16 +08:00
07dc6bf7cd Refactor sysinfo page 2023-03-09 17:17:12 +08:00
2de3f6772d fix: add aheadCnt in sysinfo (#1632)
* feat: add sync module to bi-sync mysql

* feat: fix the delay problem

* feat: fix go mod

* feat: fix the varchar(100) parse error

* fix: fix go.mod space inconsistency

* fix: fix go.mod space inconsistency

* fix: use sql builder instead of concatenation

* fix: remove serverId

* fix: fix file is not `gofumpt`-ed (gofumpt) error

* feat: add mysql bi-sync

* feat: fix some data inconsistency problems

* feat: add function atuo get server uuid

* fix: encapsulate the struct to optimize the code

* fix: fix incorrect Casdoor version in system info page

* fix: fix incorrect root path

* Update sysytem_test.go

* feat: add aheadCnt means that the commit is ahead of version several times

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-03-09 16:07:13 +08:00
3f623570fd feat: fix incorrect Casdoor version in system info page (#1631)
* feat: add sync module to bi-sync mysql

* feat: fix the delay problem

* feat: fix go mod

* feat: fix the varchar(100) parse error

* fix: fix go.mod space inconsistency

* fix: fix go.mod space inconsistency

* fix: use sql builder instead of concatenation

* fix: remove serverId

* fix: fix file is not `gofumpt`-ed (gofumpt) error

* feat: add mysql bi-sync

* feat: fix some data inconsistency problems

* feat: add function atuo get server uuid

* fix: encapsulate the struct to optimize the code

* fix: fix incorrect Casdoor version in system info page

* fix: fix incorrect root path

* Update sysytem_test.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-03-09 13:28:23 +08:00
a5dfe54a33 feat: fix LDAP page crash (#1630) 2023-03-08 21:31:55 +08:00
7c4a6fea02 fix: add "sure to delete" to i18n (#1627)
* add "Sure to delete" to i18n(#1569)

* fix: add sure to delete to i18n
2023-03-07 22:38:48 +08:00
ff4af6bb4e feat: remove ID field in enforcement (#1623) 2023-03-07 16:04:07 +08:00
5bdede5596 Use go-webauthn/webauthn 2023-03-07 13:49:04 +08:00
wht
ed052b0e6a feat: add support for SUBMAIL sms (#1621)
* feat: add support for SUBMAIL sms

* fix: add SUBMAIL SMS icon
2023-03-06 21:23:45 +08:00
16b1d0e1f0 fix: handle aliyun captcha error (#1624) 2023-03-06 21:23:03 +08:00
fea2a8cdbe feat: add bi-sync module (#1617)
* feat: add sync module to bi-sync mysql

* feat: fix the delay problem

* feat: fix go mod

* feat: fix the varchar(100) parse error

* fix: fix go.mod space inconsistency

* fix: fix go.mod space inconsistency

* fix: use sql builder instead of concatenation

* fix: remove serverId

* fix: fix file is not `gofumpt`-ed (gofumpt) error

* feat: add mysql bi-sync

* feat: fix some data inconsistency problems

* feat: add function atuo get server uuid

* fix: encapsulate the struct to optimize the code
2023-03-06 11:39:41 +08:00
9d55238cef Fix code issue 2023-03-06 00:33:26 +08:00
8427d63872 feat: fix empty value of countryCode for signup (#1620) 2023-03-05 21:52:40 +08:00
e8a7b7ee9c feat: support all captcha for login (#1619)
* refactor: captcha modal

* feat: support all captcha when login

* chore: improve i18 in loginPage.js
2023-03-05 20:31:46 +08:00
f8bc87eb4e Fix i18n error 2023-03-04 00:13:29 +08:00
3e6ef9e666 Fix forget page i18n 2023-03-03 23:55:48 +08:00
ef3d323f63 Improve SMS code 2023-03-03 22:44:22 +08:00
wht
aad9201b24 feat: add SMS test feature (#1606)
* feat: add SMS test

* fix: Add missing translation

* fix: Delete redundant information

* fix: remove unnecessary field

* Update sms.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-03-03 22:15:02 +08:00
46f090361e Improve init_data json 2023-03-03 21:32:06 +08:00
1ae6adff8e fix(secure): remove user list from roles and permissions field to avoid leaking userlist (#1614)
* fix(secure): remove user list from roles and permissions field to avoid leaking userlist

Signed-off-by: fengxsong <fengxsong@outlook.com>

* Update permission.go

* Update role.go

---------

Signed-off-by: fengxsong <fengxsong@outlook.com>
Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-03-03 18:18:41 +08:00
59c95ca8a0 feat: fix ID parsing bug when calling api/logout (#1611)
Co-authored-by: Zayn Xie <84443886+xiaoniuren99@users.noreply.github.com>
2023-03-03 14:26:31 +08:00
ca1b5feb78 Improve default captcha UI 2023-03-02 22:04:37 +08:00
e50c832ff9 Fix login width 2023-03-02 20:49:13 +08:00
8696b08db2 fix: empty countryCode of current account causes crash (#1603)
* fix: empty countryCode of current account cause crush

* Update UserEditPage.js

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2023-03-01 22:09:48 +08:00
d21ae8a478 feat: support making configs in values.yaml (#1595)
Signed-off-by: fengxsong <fengxsong@outlook.com>
2023-03-01 20:17:04 +08:00
db401b2046 ci: add migration ci test (#1600)
* feat: add migration ci test

* feat: add migration ci test

* feat: add migration ci test

---------

Co-authored-by: Zayn Xie <84443886+xiaoniuren99@users.noreply.github.com>
2023-03-01 17:30:08 +08:00
7181489da0 fix: OIDC Userinfo API response for scope profile (#1598) 2023-03-01 16:56:39 +08:00
e21087aa50 feat: refactor reset password api and forgetPage.js (#1601) 2023-03-01 15:57:42 +08:00
b38f2218a3 feat: add basic MySQL sync functionality (#1575)
* feat: add sync module to bi-sync mysql

* feat: fix the delay problem

* feat: fix go mod

* feat: fix the varchar(100) parse error

* fix: fix go.mod space inconsistency

* fix: fix go.mod space inconsistency

* fix: use sql builder instead of concatenation

* fix: remove serverId

* fix: fix file is not `gofumpt`-ed (gofumpt) error
2023-02-28 16:48:06 +08:00
afd3c4ed25 fix: fix bug form country code init error (#1591) 2023-02-27 22:07:28 +08:00
5caceb4ae2 feat: fix bug that signup country code is undefined (#1590)
* feat: fix signup country code is undefined

* refactor: valid phone number in CN
2023-02-27 20:10:59 +08:00
f5672357e6 fix resetting phone bug 2023-02-25 15:46:54 +08:00
181e7c8c7d Refactor out getCountryCodeOption() 2023-02-25 15:25:47 +08:00
36c5a9d09b Sort country list 2023-02-25 15:08:08 +08:00
9acb3c499e Can search country code 2023-02-25 14:57:23 +08:00
0e9a3b0f30 don't update provider in preview 2023-02-25 12:31:08 +08:00
d104a292e7 fix normal user phone edit control 2023-02-25 11:47:34 +08:00
8fbd5b1a74 disable demo prompt for get-organizations API 2023-02-25 11:01:48 +08:00
f5a05ac534 improve application homepage 2023-02-25 10:50:50 +08:00
05fade1d05 fix role list link error 2023-02-25 09:39:19 +08:00
8aefa02036 fix message length 2023-02-25 08:36:24 +08:00
3b6ec3e7c4 feat: improve saml idp err message (#1584) 2023-02-24 21:20:57 +08:00
910816c7a3 Fix bug in GetLanguage() 2023-02-24 20:17:23 +08:00
412a8b5da7 fix: init name is inconsistent with frontend (#1583) 2023-02-24 14:28:34 +08:00
8ebd16a14e feat: fix resetting email and phone bug (#1579) 2023-02-23 18:06:13 +08:00
44ec854465 Refactor getClientIdLabel() and getClientSecretLabel() 2023-02-23 17:57:46 +08:00
26e87b0d98 feat: fix compatibility with lower version browsers like Chrome 87 (#1578) 2023-02-22 20:57:57 +08:00
7e0ea0b8d9 Fix missing accountItem crash bug 2023-02-22 12:16:05 +08:00
ace8e9da06 Refactor getAppIdRow() 2023-02-22 12:10:55 +08:00
aac8714d72 feat: handle aliyun SMS response error (#1577) 2023-02-21 20:08:23 +08:00
e71e41b343 feat: fix captcha none type bug (#1572) 2023-02-19 16:56:51 +08:00
6131286cbd Add getBuiltInAccountItems() 2023-02-19 09:45:06 +08:00
3bda8fb9dc Remove Migrator_1_245_0_PR_1557 2023-02-19 09:38:57 +08:00
11f55a474c refactor: New Crowdin translations (#1440)
* 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-02-18 23:46:44 +08:00
4806e76cf6 Fix demo mode default login 2023-02-18 23:43:22 +08:00
edbd3d4018 Add i18n banner 2023-02-18 18:09:43 +08:00
3f0a741e6c Improve i18n languages 2023-02-18 17:35:36 +08:00
d273fdd670 Use username as saml:NameID 2023-02-18 16:42:45 +08:00
3ae81716b9 Fix getLdaps() GET 2023-02-18 16:27:47 +08:00
3a70f4e788 Add Setting.isResponseDenied() 2023-02-18 16:21:12 +08:00
842d4865b2 Fix router error message's i18n 2023-02-18 16:11:23 +08:00
19fb7273bb fix: detect access denied by response message for demo mode (#1565)
fix: detect access denied by response message for demo mode
2023-02-18 12:12:54 +08:00
943bd82731 feat: fix migrator_1_245_0_PR_1557.go (#1564) 2023-02-18 10:58:14 +08:00
f2f962b893 fix: refactor functions and code (#1559) 2023-02-18 09:31:58 +08:00
eb72c9f273 feat: support multiple country codes for sending SMS (#1557)
* feat: support multiple country code

* feat: improve UI

* feat: migrate accountItem

* fix: Aliyun compatible

* fix: phone validate

* fix: typo
2023-02-16 22:53:28 +08:00
4605938f8e Refactor FetchFilter.js code 2023-02-16 22:50:08 +08:00
14fa914e6f feat: add IsDemoMode for frontend (#1555)
* feat: add `IsDemoMode` for frontend

* fix: add i18n

* fix: support autologin and go same page

* fix: use i18n for button text
2023-02-16 20:36:30 +08:00
e877045671 feat: fix CAS login crash bug (#1549) 2023-02-14 16:52:15 +08:00
29f1ec08a2 fix: fix CI error by auto waiting for localhost:7001 to start up (#1548) 2023-02-14 14:50:58 +08:00
389744a27d feat: change claims to claimsWithoutThirdIdp when gen token (#1552) 2023-02-14 09:33:46 +08:00
dc7b66822d feat: change token ExpiresIn to second (#1550) 2023-02-14 09:18:30 +08:00
efacf8226c fix: session Id error (#1554) 2023-02-13 22:58:26 +08:00
6beb68dcce fix: some bugs in session module when testing single-log-in (#1547)
Co-authored-by: Zayn Xie <84443886+xiaoniuren99@users.noreply.github.com>
2023-02-13 18:16:31 +08:00
c9b990a319 Add removeExtraSessionIds() 2023-02-12 21:11:16 +08:00
eedcde3aa5 Refactor session.go 2023-02-12 21:06:08 +08:00
950a274b23 fix: region don't display in userEditPage (#1544) 2023-02-12 18:56:56 +08:00
478bd05db4 Improve error handling in migrator 2023-02-12 10:39:20 +08:00
9256791420 feat: app session control and db migrate (#1539)
* feat: integrate application session management into Casdoor's session management (#774) && standardized the database migration process (#1533)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774)

* feat: integrate application session management into Casdoor's session management (#774) && standardized the database migration process

* feat: integrate application session management into Casdoor's session management (#774) && standardized the database migration process

* feat: integrate application session management into Casdoor's session management (#774) && standardized the database migration process

---------

Co-authored-by: Zayn Xie <84443886+xiaoniuren99@users.noreply.github.com>

* fix: migrate err

* fix: migrate err

* feat: app session control and db migrate

* feat: app session control and db migrate

* feat: app session control and db migrate

---------

Co-authored-by: Zayn Xie <84443886+xiaoniuren99@users.noreply.github.com>
2023-02-12 09:33:24 +08:00
6f2ef32d02 Update xorm.io/core to v0.7.3 2023-02-11 17:46:34 +08:00
8b8c866fd2 feat: replace gomail and disable the Email username check for Mailtrap (#1538) 2023-02-11 15:59:23 +08:00
6f7230e949 feat: support refresh token on GetOAuthToken (#1536) 2023-02-10 23:52:13 +08:00
wht
9558bb4167 feat: fix file name length problem (#1534) 2023-02-10 20:27:20 +08:00
04567babf8 feat: fix click MenuItem blank part invalid bug (#1535) 2023-02-10 18:11:15 +08:00
543b316942 feat: update swagger parameter id description (#1532) 2023-02-10 10:42:16 +08:00
e2b6e8ee6e chore: unify migrate database way (#1530) 2023-02-09 19:28:15 +08:00
wht
e7e0518517 feat: fix the upload file name contains space problem (#1527) 2023-02-07 23:26:17 +08:00
943aa61869 feat: add provider icons and menus (#1522)
* fix: add provider icons(email and captcha) and menus

* fix: add provider icons and menus
2023-02-06 20:28:40 +08:00
wht
fcc75dd3be feat: fix the Unicode filename encoding bug in storage provider (#1518) 2023-02-04 18:09:18 +08:00
8698f4111a feat: add all remaining Goth providers to Casdoor OAuth login (#1484)
* feat: add Amazon support as OAuth 3rd-party login

* refactor: comebine the same URLs

* refactor: use hyper component to create login button

* feat: add all remaining Goth providers to Casdoor OAuth login

* refactor: remove redundant props

* fix: check provider auth url and params
2023-02-04 12:20:18 +08:00
fdccb8b22b feat: Test whether the page can be accessed (#1517)
* feat: add new line

* feat: Test whether the page can be accessed

* feat: Change the e2e order

* feat: add Test Retries

* feat: change yarn.lock

* feat: add new line
2023-02-03 19:59:28 +08:00
19e7d0b0bd refactor: improve code reuse rate (#1515) 2023-02-02 16:43:51 +08:00
f6a502f7ff feat: add user password in ldap server search result (#1513)
* fix: ldap server search return inconsistent cn attribute

* feat: add user password in ldap server search result
2023-02-02 15:33:44 +08:00
b34e16b145 fix: table do not have unique key (#1512) 2023-02-02 13:53:18 +08:00
11b56c340f Add refineUser() in generateJwtToken() 2023-02-02 00:34:56 +08:00
cc6ea1b60e feat: fix application edit page crash and language icon position (#1511)
* fix: widget position and color

* feat: fix applicationEdit crush
2023-02-01 23:11:48 +08:00
95b32d5ebf feat: support customize theme (#1500)
* refactor: simplify functions and improve variable naming

* feat: add themeEditor component

* feat: support customize theme

* chore: resolve conflict and add LICENCE

* chore: format code

* refactor: use icon replace background url

* feat: improve organization and application theme editor
2023-02-01 22:06:40 +08:00
b47baa06e1 fix: remove "Agreement" in edit application error (#1506) 2023-01-31 22:56:19 +08:00
wht
24a824d394 feat: return the correct error message in the Edit Model (#1504) 2023-01-30 22:19:42 +08:00
75b8357de8 Add properties to UserWithoutThirdIdp 2023-01-29 21:51:01 +08:00
087405dad2 Fix isAllowedInDemoMode() 2023-01-26 17:56:29 +08:00
6a6a1fa920 feat: fix missing phone number prefix in login screen (#1492)
fix: #1489
2023-01-24 23:19:44 +08:00
907d18d2e9 Fix missing roles and permissions in user table 2023-01-23 00:36:55 +08:00
a728e083eb feat: reduce the size of token's user object (#1487)
* fix: Reduce the size of token, especially the user object (#1170)

* fix: Reduce the size of token, especially the user object (#1170)

* fix: Reduce the size of token, especially the user object (#1170)

Co-authored-by: Zayn Xie <84443886+xiaoniuren99@users.noreply.github.com>
2023-01-21 09:30:23 +08:00
457e6208ad feat: terms of use auto selected (#1485) 2023-01-19 20:31:21 +08:00
d10b1347a8 feat: add terms of use in signin page (#1476)
* feat: extract terms of use renderer

* fix: layout

* fix: form styling

* fix: required state

* feat: application terms of use setting

* fix: refactor getTermsOfUseContent

* fix: refactor renderers
2023-01-19 18:39:24 +08:00
f5b7f8cb45 chore(frontend): remove import of the third-party js script (#1436)
Signed-off-by: qwqcode <qwqcode@gmail.com>

Signed-off-by: qwqcode <qwqcode@gmail.com>
2023-01-19 11:31:27 +08:00
592 changed files with 114651 additions and 31679 deletions

8
.gitattributes vendored
View File

@ -1,5 +1,5 @@
*.go linguist-detectable=true
*.js linguist-detectable=false
# Declare files that will always have LF line endings on checkout.
# Git will always convert line endings to LF on checkout. You should use this for files that must keep LF endings, even on Windows.
*.go linguist-detectable=true
*.js linguist-detectable=false
# Declare files that will always have LF line endings on checkout.
# Git will always convert line endings to LF on checkout. You should use this for files that must keep LF endings, even on Windows.
*.sh text eol=lf

View File

@ -1,6 +1,6 @@
name: Build
on: [push, pull_request]
on: [ push, pull_request ]
jobs:
@ -9,18 +9,19 @@ jobs:
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
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
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '^1.16.5'
cache-dependency-path: ./go.mod
- name: Tests
run: |
go test -v $(go list ./...) -tags skipCi
@ -31,14 +32,12 @@ jobs:
runs-on: ubuntu-latest
needs: [ go-tests ]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
# cache
- uses: c-hive/gha-yarn-cache@v2
with:
directory: ./web
node-version: 20
cache: 'yarn'
cache-dependency-path: ./web/yarn.lock
- run: yarn install && CI=false yarn run build
working-directory: ./web
@ -47,10 +46,11 @@ jobs:
runs-on: ubuntu-latest
needs: [ go-tests ]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '^1.16.5'
cache-dependency-path: ./go.mod
- run: go version
- name: Build
run: |
@ -63,13 +63,14 @@ jobs:
needs: [ go-tests ]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: '^1.16.5'
cache: false
# gen a dummy config file
- run: touch dummy.yml
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
@ -79,50 +80,50 @@ jobs:
e2e:
name: e2e-test
runs-on: ubuntu-latest
needs: [ frontend, backend, linter ]
needs: [ go-tests ]
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
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
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '^1.16.5'
- uses: actions/setup-node@v2
with:
node-version: 16
- name: front install
run: yarn install
working-directory: ./web
- name: front start
run: nohup yarn start &
working-directory: ./web
- name: back start
cache-dependency-path: ./go.mod
- name: start backend
run: nohup go run ./main.go &
working-directory: ./
- name: Sleep for starting
run: sleep 90s
shell: bash
- name: e2e
run: npx cypress run --spec "**/e2e/**.cy.js"
- uses: actions/setup-node@v3
with:
node-version: 20
cache: 'yarn'
cache-dependency-path: ./web/yarn.lock
- run: yarn install
working-directory: ./web
- uses: cypress-io/github-action@v5
with:
browser: chrome
start: yarn start
wait-on: 'http://localhost:7001'
wait-on-timeout: 210
working-directory: ./web
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-screenshots
path: ./web/cypress/screenshots
- uses: actions/upload-artifact@v3
- uses: actions/upload-artifact@v4
if: always()
with:
name: cypress-videos
path: ./web/cypress/videos
path: ./web/cypress/videos
release-and-push:
name: Release And Push
@ -131,13 +132,13 @@ jobs:
needs: [ frontend, backend, linter, e2e ]
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
fetch-depth: -1
- name: Setup Node.js
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 16
node-version: 20
- name: Fetch Previous version
id: get-previous-tag
@ -146,7 +147,7 @@ jobs:
- name: Release
run: yarn global add semantic-release@17.4.4 && semantic-release
env:
GH_TOKEN: ${{ secrets.GH_BOT_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Fetch Current version
id: get-current-tag
@ -167,10 +168,8 @@ jobs:
elif [ ${old_array[1]} != ${new_array[1]} ]
then
echo ::set-output name=push::'true'
else
echo ::set-output name=push::'false'
fi
- name: Set up QEMU
@ -193,6 +192,7 @@ jobs:
uses: docker/build-push-action@v3
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with:
context: .
target: STANDARD
platforms: linux/amd64,linux/arm64
push: true
@ -202,7 +202,38 @@ jobs:
uses: docker/build-push-action@v3
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with:
context: .
target: ALLINONE
platforms: linux/amd64,linux/arm64
push: true
tags: casbin/casdoor-all-in-one:${{steps.get-current-tag.outputs.tag }},casbin/casdoor-all-in-one:latest
- uses: actions/checkout@v3
if: steps.should_push.outputs.push=='true'
with:
repository: casdoor/casdoor-helm
ref: 'master'
token: ${{ secrets.GH_BOT_TOKEN }}
- name: Update Helm Chart
if: steps.should_push.outputs.push=='true'
run: |
# Set the appVersion and version of the chart to the current tag
sed -i "s/appVersion: .*/appVersion: ${{steps.get-current-tag.outputs.tag }}/g" ./charts/casdoor/Chart.yaml
sed -i "s/version: .*/version: ${{steps.get-current-tag.outputs.tag }}/g" ./charts/casdoor/Chart.yaml
REGISTRY=oci://registry-1.docker.io/casbin
cd charts/casdoor
helm package .
PKG_NAME=$(ls *.tgz)
helm repo index . --url $REGISTRY --merge index.yaml
helm push $PKG_NAME $REGISTRY
rm $PKG_NAME
# Commit and push the changes back to the repository
git config --global user.name "casbin-bot"
git config --global user.email "bot@casbin.org"
git add Chart.yaml index.yaml
git commit -m "chore(helm): bump helm charts appVersion to ${{steps.get-current-tag.outputs.tag }}"
git tag ${{steps.get-current-tag.outputs.tag }}
git push origin HEAD:master --follow-tags

8
.gitignore vendored
View File

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

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

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

View File

@ -1,15 +1,14 @@
FROM node:16.13.0 AS FRONT
FROM --platform=$BUILDPLATFORM node:18.19.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
RUN yarn install --frozen-lockfile --network-timeout 1000000 && NODE_OPTIONS="--max-old-space-size=4096" yarn run build
FROM golang:1.17.5 AS BACK
FROM --platform=$BUILDPLATFORM golang:1.21.13 AS BACK
WORKDIR /go/src/casdoor
COPY . .
RUN ./build.sh
RUN go test -v -run TestGetVersionInfo ./util/system_test.go ./util/system.go > version_info.txt
FROM alpine:latest AS STANDARD
LABEL MAINTAINER="https://casdoor.org/"
@ -20,6 +19,7 @@ ENV BUILDX_ARCH="${TARGETOS:-linux}_${TARGETARCH:-amd64}"
RUN sed -i 's/https/http/' /etc/apk/repositories
RUN apk add --update sudo
RUN apk add tzdata
RUN apk add curl
RUN apk add ca-certificates && update-ca-certificates
@ -34,6 +34,7 @@ WORKDIR /
COPY --from=BACK --chown=$USER:$USER /go/src/casdoor/server_${BUILDX_ARCH} ./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
COPY --from=FRONT --chown=$USER:$USER /web/build ./web/build
ENTRYPOINT ["/server"]
@ -61,6 +62,7 @@ COPY --from=BACK /go/src/casdoor/server_${BUILDX_ARCH} ./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
ENTRYPOINT ["/bin/bash"]

View File

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

190
README.md
View File

@ -1,88 +1,102 @@
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
<h3 align="center">A UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.</h3>
<p align="center">
<a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
<a href="https://hub.docker.com/r/casbin/casdoor">
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
</a>
<a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml">
<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">
</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">
</a>
</p>
<p align="center">
<a href="https://goreportcard.com/report/github.com/casdoor/casdoor">
<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">
</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">
</a>
<a href="#">
<img alt="GitHub stars" src="https://img.shields.io/github/stars/casbin/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">
</a>
<a href="https://crowdin.com/project/casdoor-site">
<img alt="Crowdin" src="https://badges.crowdin.net/casdoor-site/localized.svg">
</a>
<a href="https://gitter.im/casbin/casdoor">
<img alt="Gitter" src="https://badges.gitter.im/casbin/casdoor.svg">
</a>
</p>
## Online demo
- Read-only site: https://door.casdoor.com (any modification operation will fail)
- Writable site: https://demo.casdoor.com (original data will be restored for every 5 minutes)
## Documentation
https://casdoor.org
## Install
- By source code: https://casdoor.org/docs/basic/server-installation
- By Docker: https://casdoor.org/docs/basic/try-with-docker
## How to connect to Casdoor?
https://casdoor.org/docs/how-to-connect/overview
## Casdoor Public API
- Docs: https://casdoor.org/docs/basic/public-api
- Swagger: https://door.casdoor.com/swagger
## Integrations
https://casdoor.org/docs/category/integrations
## How to contact?
- Gitter: https://gitter.im/casbin/casdoor
- Forum: https://forum.casbin.com
- Contact: https://tawk.to/chat/623352fea34c2456412b8c51/1fuc7od6e
## Contribute
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
### I18n translation
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the `web/` directory, please remember to add what you have added to the `web/src/locales/en/data.json` file.
## License
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
<h3 align="center">An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS</h3>
<p align="center">
<a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">
</a>
<a href="https://hub.docker.com/r/casbin/casdoor">
<img alt="docker pull casbin/casdoor" src="https://img.shields.io/docker/pulls/casbin/casdoor.svg">
</a>
<a href="https://github.com/casdoor/casdoor/actions/workflows/build.yml">
<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/casdoor/casdoor.svg">
</a>
<a href="https://hub.docker.com/r/casbin/casdoor">
<img alt="Docker Image Version (latest semver)" src="https://img.shields.io/badge/Docker%20Hub-latest-brightgreen">
</a>
</p>
<p align="center">
<a href="https://goreportcard.com/report/github.com/casdoor/casdoor">
<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/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/casdoor/casdoor?style=flat-square">
</a>
<a href="#">
<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/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">
</a>
<a href="https://discord.gg/5rPsrAzK7S">
<img alt="Discord" src="https://img.shields.io/discord/1022748306096537660?style=flat-square&logo=discord&label=discord&color=5865F2">
</a>
</p>
<p align="center">
<sup>Sponsored by</sup>
<br>
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.casbin.org/img/stytch-white.png">
<source media="(prefers-color-scheme: light)" srcset="https://cdn.casbin.org/img/stytch-charcoal.png">
<img src="https://cdn.casbin.org/img/stytch-charcoal.png" width="275">
</picture>
</a><br/>
<a href="https://stytch.com/docs?utm_source=oss-sponsorship&utm_medium=paid_sponsorship&utm_campaign=casbin"><b>Build auth with fraud prevention, faster.</b><br/> Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.</a>
<br>
</p>
## Online demo
- Read-only site: https://door.casdoor.com (any modification operation will fail)
- Writable site: https://demo.casdoor.com (original data will be restored for every 5 minutes)
## Documentation
https://casdoor.org
## Install
- By source code: https://casdoor.org/docs/basic/server-installation
- By Docker: https://casdoor.org/docs/basic/try-with-docker
- By Kubernetes Helm: https://casdoor.org/docs/basic/try-with-helm
## How to connect to Casdoor?
https://casdoor.org/docs/how-to-connect/overview
## Casdoor Public API
- Docs: https://casdoor.org/docs/basic/public-api
- Swagger: https://door.casdoor.com/swagger
## Integrations
https://casdoor.org/docs/category/integrations
## How to contact?
- Discord: https://discord.gg/5rPsrAzK7S
- Contact: https://casdoor.org/help
## Contribute
For casdoor, if you have any questions, you can give Issues, or you can also directly start Pull Requests(but we recommend giving issues first to communicate with the community).
### I18n translation
If you are contributing to casdoor, please note that we use [Crowdin](https://crowdin.com/project/casdoor-site) as translating platform and i18next as translating tool. When you add some words using i18next in the `web/` directory, please remember to add what you have added to the `web/src/locales/en/data.json` file.
## License
[Apache-2.0](https://github.com/casdoor/casdoor/blob/master/LICENSE)

View File

@ -15,63 +15,24 @@
package authz
import (
"fmt"
"strings"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v3"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
stringadapter "github.com/qiangmzsx/string-adapter/v2"
)
var Enforcer *casbin.Enforcer
func InitAuthz() {
var err error
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
driverName := conf.GetConfigString("driverName")
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
a, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, "casbin_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}
modelText := `
[request_definition]
r = subOwner, subName, method, urlPath, objOwner, objName
[policy_definition]
p = subOwner, subName, method, urlPath, objOwner, objName
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
(r.subName == p.subName || p.subName == "*" || r.subName != "anonymous" && p.subName == "!anonymous") && \
(r.method == p.method || p.method == "*") && \
(r.urlPath == p.urlPath || p.urlPath == "*") && \
(r.objOwner == p.objOwner || p.objOwner == "*") && \
(r.objName == p.objName || p.objName == "*") || \
(r.subOwner == r.objOwner && r.subName == r.objName)
`
m, err := model.NewModelFromString(modelText)
if err != nil {
panic(err)
}
Enforcer, err = casbin.NewEnforcer(m, a)
func InitApi() {
e, err := object.GetInitializedEnforcer(util.GetId("built-in", "api-enforcer-built-in"))
if err != nil {
panic(err)
}
Enforcer = e.Enforcer
Enforcer.ClearPolicy()
// if len(Enforcer.GetPolicy()) == 0 {
@ -80,15 +41,21 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
p, built-in, *, *, *, *, *
p, app, *, *, *, *, *
p, *, *, POST, /api/signup, *, *
p, *, *, POST, /api/get-email-and-phone, *, *
p, *, *, GET, /api/get-email-and-phone, *, *
p, *, *, POST, /api/login, *, *
p, *, *, GET, /api/get-app-login, *, *
p, *, *, POST, /api/logout, *, *
p, *, *, GET, /api/logout, *, *
p, *, *, POST, /api/callback, *, *
p, *, *, POST, /api/device-auth, *, *
p, *, *, GET, /api/get-account, *, *
p, *, *, GET, /api/userinfo, *, *
p, *, *, POST, /api/webhook, *, *
p, *, *, GET, /api/user, *, *
p, *, *, GET, /api/health, *, *
p, *, *, *, /api/webhook, *, *
p, *, *, GET, /api/get-qrcode, *, *
p, *, *, GET, /api/get-webhook-event, *, *
p, *, *, GET, /api/get-captcha-status, *, *
p, *, *, *, /api/login/oauth, *, *
p, *, *, GET, /api/get-application, *, *
p, *, *, GET, /api/get-organization-applications, *, *
@ -107,22 +74,40 @@ p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, *
p, *, *, GET, /api/get-captcha, *, *
p, *, *, POST, /api/verify-captcha, *, *
p, *, *, POST, /api/verify-code, *, *
p, *, *, POST, /api/reset-email-or-phone, *, *
p, *, *, POST, /api/upload-resource, *, *
p, *, *, GET, /.well-known/openid-configuration, *, *
p, *, *, GET, /.well-known/webfinger, *, *
p, *, *, *, /.well-known/jwks, *, *
p, *, *, GET, /api/get-saml-login, *, *
p, *, *, POST, /api/acs, *, *
p, *, *, GET, /api/saml/metadata, *, *
p, *, *, *, /api/saml/redirect, *, *
p, *, *, *, /cas, *, *
p, *, *, *, /scim, *, *
p, *, *, *, /api/webauthn, *, *
p, *, *, GET, /api/get-release, *, *
p, *, *, GET, /api/get-default-application, *, *
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, *, *
p, *, *, GET, /api/get-all-objects, *, *
p, *, *, GET, /api/get-all-actions, *, *
p, *, *, GET, /api/get-all-roles, *, *
p, *, *, GET, /api/run-casbin-command, *, *
p, *, *, POST, /api/refresh-engines, *, *
p, *, *, GET, /api/get-invitation-info, *, *
p, *, *, GET, /api/faceid-signin-begin, *, *
`
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)
}
@ -144,12 +129,25 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
}
}
userId := fmt.Sprintf("%s/%s", subOwner, subName)
user := object.GetUser(userId)
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin" && subOwner == objName)) {
user, err := object.GetUser(util.GetId(subOwner, subName))
if err != nil {
panic(err)
}
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)
@ -160,11 +158,16 @@ 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" {
if strings.HasPrefix(urlPath, "/api/login") || urlPath == "/api/logout" || urlPath == "/api/signup" || urlPath == "/api/callback" || urlPath == "/api/send-verification-code" || urlPath == "/api/send-email" || urlPath == "/api/verify-captcha" || urlPath == "/api/verify-code" || urlPath == "/api/check-user-password" || strings.HasPrefix(urlPath, "/api/mfa/") || urlPath == "/api/webhook" || urlPath == "/api/get-qrcode" || urlPath == "/api/refresh-engines" {
return true
} else if urlPath == "/api/update-user" {
// Allow ordinary users to update their own information
if subOwner == objOwner && subName == objName && !(subOwner == "built-in" && subName == "admin") {
if (subOwner == objOwner && subName == objName || subOwner == "app") && !(subOwner == "built-in" && subName == "admin") {
return true
}
return false
} else if urlPath == "/api/upload-resource" {
if subOwner == "app" && subName == "app-casibase" {
return true
}
return false

View File

@ -8,5 +8,6 @@ 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 .

View File

@ -15,22 +15,51 @@
package captcha
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
"github.com/casdoor/casdoor/util"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
openapiutil "github.com/alibabacloud-go/openapi-util/service"
teaUtil "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
)
const AliyunCaptchaVerifyUrl = "http://afs.aliyuncs.com"
const AliyunCaptchaVerifyUrl = "captcha.cn-shanghai.aliyuncs.com"
type VerifyCaptchaRequest struct {
CaptchaVerifyParam *string `json:"CaptchaVerifyParam,omitempty" xml:"CaptchaVerifyParam,omitempty"`
SceneId *string `json:"SceneId,omitempty" xml:"SceneId,omitempty"`
}
type VerifyCaptchaResponseBodyResult struct {
VerifyResult *bool `json:"VerifyResult,omitempty" xml:"VerifyResult,omitempty"`
}
type VerifyCaptchaResponseBody struct {
Code *string `json:"Code,omitempty" xml:"Code,omitempty"`
Message *string `json:"Message,omitempty" xml:"Message,omitempty"`
// Id of the request
RequestId *string `json:"RequestId,omitempty" xml:"RequestId,omitempty"`
Result *VerifyCaptchaResponseBodyResult `json:"Result,omitempty" xml:"Result,omitempty" type:"Struct"`
Success *bool `json:"Success,omitempty" xml:"Success,omitempty"`
}
type VerifyIntelligentCaptchaResponseBodyResult struct {
VerifyCode *string `json:"VerifyCode,omitempty" xml:"VerifyCode,omitempty"`
VerifyResult *bool `json:"VerifyResult,omitempty" xml:"VerifyResult,omitempty"`
}
type VerifyIntelligentCaptchaResponseBody struct {
Code *string `json:"Code,omitempty" xml:"Code,omitempty"`
Message *string `json:"Message,omitempty" xml:"Message,omitempty"`
// Id of the request
RequestId *string `json:"RequestId,omitempty" xml:"RequestId,omitempty"`
Result *VerifyIntelligentCaptchaResponseBodyResult `json:"Result,omitempty" xml:"Result,omitempty" type:"Struct"`
Success *bool `json:"Success,omitempty" xml:"Success,omitempty"`
}
type VerifyIntelligentCaptchaResponse struct {
Headers map[string]*string `json:"headers,omitempty" xml:"headers,omitempty" require:"true"`
StatusCode *int32 `json:"statusCode,omitempty" xml:"statusCode,omitempty" require:"true"`
Body *VerifyIntelligentCaptchaResponseBody `json:"body,omitempty" xml:"body,omitempty" require:"true"`
}
type AliyunCaptchaProvider struct{}
func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
@ -38,67 +67,69 @@ func NewAliyunCaptchaProvider() *AliyunCaptchaProvider {
return captcha
}
func contentEscape(str string) string {
str = strings.Replace(str, " ", "%20", -1)
str = url.QueryEscape(str)
return str
}
func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
pathData, err := url.ParseQuery(token)
if err != nil {
return false, err
}
pathData["Action"] = []string{"AuthenticateSig"}
pathData["Format"] = []string{"json"}
pathData["SignatureMethod"] = []string{"HMAC-SHA1"}
pathData["SignatureNonce"] = []string{strconv.FormatInt(time.Now().UnixNano(), 10)}
pathData["SignatureVersion"] = []string{"1.0"}
pathData["Timestamp"] = []string{time.Now().UTC().Format("2006-01-02T15:04:05Z")}
pathData["Version"] = []string{"2018-01-12"}
var keys []string
for k := range pathData {
keys = append(keys, k)
}
sort.Strings(keys)
sortQuery := ""
for _, k := range keys {
sortQuery += k + "=" + contentEscape(pathData[k][0]) + "&"
}
sortQuery = strings.TrimSuffix(sortQuery, "&")
stringToSign := fmt.Sprintf("GET&%s&%s", url.QueryEscape("/"), url.QueryEscape(sortQuery))
signature := util.GetHmacSha1(clientSecret+"&", stringToSign)
resp, err := http.Get(fmt.Sprintf("%s?%s&Signature=%s", AliyunCaptchaVerifyUrl, sortQuery, url.QueryEscape(signature)))
if err != nil {
return false, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
type captchaResponse struct {
Code int `json:"Code"`
Msg string `json:"Msg"`
}
captchaResp := &captchaResponse{}
err = json.Unmarshal(body, captchaResp)
if err != nil {
return false, err
}
if captchaResp.Code != 100 {
return false, errors.New(captchaResp.Msg)
}
return true, nil
func (captcha *AliyunCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
config := &openapi.Config{}
config.Endpoint = tea.String(AliyunCaptchaVerifyUrl)
config.ConnectTimeout = tea.Int(5000)
config.ReadTimeout = tea.Int(5000)
config.AccessKeyId = tea.String(clientId)
config.AccessKeySecret = tea.String(clientSecret)
client := new(openapi.Client)
err := client.Init(config)
if err != nil {
return false, err
}
request := VerifyCaptchaRequest{CaptchaVerifyParam: tea.String(token), SceneId: tea.String(clientId2)}
err = teaUtil.ValidateModel(&request)
if err != nil {
return false, err
}
runtime := &teaUtil.RuntimeOptions{}
body := map[string]interface{}{}
if !tea.BoolValue(teaUtil.IsUnset(request.CaptchaVerifyParam)) {
body["CaptchaVerifyParam"] = request.CaptchaVerifyParam
}
if !tea.BoolValue(teaUtil.IsUnset(request.SceneId)) {
body["SceneId"] = request.SceneId
}
req := &openapi.OpenApiRequest{
Body: openapiutil.ParseToMap(body),
}
params := &openapi.Params{
Action: tea.String("VerifyIntelligentCaptcha"),
Version: tea.String("2023-03-05"),
Protocol: tea.String("HTTPS"),
Pathname: tea.String("/"),
Method: tea.String("POST"),
AuthType: tea.String("AK"),
Style: tea.String("RPC"),
ReqBodyType: tea.String("formData"),
BodyType: tea.String("json"),
}
res := &VerifyIntelligentCaptchaResponse{}
resBody, err := client.CallApi(params, req, runtime)
if err != nil {
return false, err
}
err = tea.Convert(resBody, &res)
if err != nil {
return false, err
}
if res.Body.Result.VerifyResult != nil && *res.Body.Result.VerifyResult {
return true, nil
}
return false, nil
}

View File

@ -23,6 +23,6 @@ func NewDefaultCaptchaProvider() *DefaultCaptchaProvider {
return captcha
}
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
func (captcha *DefaultCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
return object.VerifyCaptcha(clientSecret, token), nil
}

View File

@ -35,7 +35,7 @@ func NewGEETESTCaptchaProvider() *GEETESTCaptchaProvider {
return captcha
}
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
func (captcha *GEETESTCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
pathData, err := url.ParseQuery(token)
if err != nil {
return false, err

View File

@ -32,7 +32,7 @@ func NewHCaptchaProvider() *HCaptchaProvider {
return captcha
}
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
func (captcha *HCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
reqData := url.Values{
"secret": {clientSecret},
"response": {token},

View File

@ -17,31 +17,37 @@ package captcha
import "fmt"
type CaptchaProvider interface {
VerifyCaptcha(token, clientSecret string) (bool, error)
VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error)
}
func GetCaptchaProvider(captchaType string) CaptchaProvider {
if captchaType == "Default" {
switch captchaType {
case "Default":
return NewDefaultCaptchaProvider()
} else if captchaType == "reCAPTCHA" {
case "reCAPTCHA":
return NewReCaptchaProvider()
} else if captchaType == "hCaptcha" {
return NewHCaptchaProvider()
} else if captchaType == "Aliyun Captcha" {
case "reCAPTCHA v2":
return NewReCaptchaProvider()
case "reCAPTCHA v3":
return NewReCaptchaProvider()
case "Aliyun Captcha":
return NewAliyunCaptchaProvider()
} else if captchaType == "GEETEST" {
case "hCaptcha":
return NewHCaptchaProvider()
case "GEETEST":
return NewGEETESTCaptchaProvider()
} else if captchaType == "Cloudflare Turnstile" {
case "Cloudflare Turnstile":
return NewCloudflareTurnstileProvider()
}
return nil
}
func VerifyCaptchaByCaptchaType(captchaType, token, clientSecret string) (bool, error) {
func VerifyCaptchaByCaptchaType(captchaType, token, clientId, clientSecret, clientId2 string) (bool, error) {
provider := GetCaptchaProvider(captchaType)
if provider == nil {
return false, fmt.Errorf("invalid captcha provider: %s", captchaType)
}
return provider.VerifyCaptcha(token, clientSecret)
return provider.VerifyCaptcha(token, clientId, clientSecret, clientId2)
}

View File

@ -32,7 +32,7 @@ func NewReCaptchaProvider() *ReCaptchaProvider {
return captcha
}
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
func (captcha *ReCaptchaProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
reqData := url.Values{
"secret": {clientSecret},
"response": {token},

View File

@ -32,7 +32,7 @@ func NewCloudflareTurnstileProvider() *CloudflareTurnstileProvider {
return captcha
}
func (captcha *CloudflareTurnstileProvider) VerifyCaptcha(token, clientSecret string) (bool, error) {
func (captcha *CloudflareTurnstileProvider) VerifyCaptcha(token, clientId, clientSecret, clientId2 string) (bool, error) {
reqData := url.Values{
"secret": {clientSecret},
"response": {token},

View File

@ -8,17 +8,30 @@ 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
isUsernameLowered = false
origin =
originFrontend =
staticBaseUrl = "https://cdn.casbin.org"
isDemoMode = false
batchSize = 100
enableErrorMask = false
enableGzip = true
inactiveTimeoutMinutes =
ldapServerPort = 389
languages = en,zh,es,fr,de,ja,ko,ru
ldapsCertId = ""
ldapsServerPort = 636
radiusServerPort = 1812
radiusDefaultOrganization = "built-in"
radiusSecret = "secret"
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
initDataNewOnly = false
initDataFile = "./init_data.json"
frontendBaseDir = "../cc_0"

View File

@ -15,7 +15,6 @@
package conf
import (
"encoding/json"
"fmt"
"os"
"runtime"
@ -25,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"}
@ -45,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,20 +46,21 @@ func GetConfigString(key string) string {
if res == "" {
if key == "staticBaseUrl" {
res = "https://cdn.casbin.org"
} else if key == "logConfig" {
res = fmt.Sprintf("{\"filename\": \"logs/%s.log\", \"maxdays\":99999, \"perm\":\"0770\"}", beego.AppConfig.String("appname"))
}
}
return res
}
func GetConfigBool(key string) (bool, error) {
func GetConfigBool(key string) bool {
value := GetConfigString(key)
if value == "true" {
return true, nil
} else if value == "false" {
return false, nil
return true
} else {
return false
}
return false, fmt.Errorf("value %s cannot be converted into bool", value)
}
func GetConfigInt64(key string) (int64, error) {
@ -91,7 +71,10 @@ func GetConfigInt64(key string) (int64, error) {
func GetConfigDataSourceName() string {
dataSourceName := GetConfigString("dataSourceName")
return ReplaceDataSourceNameByDocker(dataSourceName)
}
func ReplaceDataSourceNameByDocker(dataSourceName string) string {
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
if runningInDocker == "true" {
// https://stackoverflow.com/questions/48546124/what-is-linux-equivalent-of-host-docker-internal
@ -101,10 +84,21 @@ func GetConfigDataSourceName() string {
dataSourceName = strings.ReplaceAll(dataSourceName, "localhost", "host.docker.internal")
}
}
return dataSourceName
}
func GetLanguage(language string) string {
if language == "" || language == "*" {
return "en"
}
if len(language) != 2 || language == "nu" {
return "en"
} else {
return language
}
}
func IsDemoMode() bool {
return strings.ToLower(GetConfigString("isDemoMode")) == "true"
}
@ -116,17 +110,3 @@ func GetConfigBatchSize() int {
}
return res
}
func GetConfigQuota() *Quota {
return quota
}
func GetConfigRealDataSourceName(driverName string) string {
var dataSourceName string
if driverName != "mysql" {
dataSourceName = GetConfigDataSourceName()
} else {
dataSourceName = GetConfigDataSourceName() + GetConfigString("dbName")
}
return dataSourceName
}

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

@ -87,7 +87,7 @@ func TestGetConfBool(t *testing.T) {
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual, err := GetConfigBool(scenery.input)
actual := GetConfigBool(scenery.input)
assert.Nil(t, err)
assert.Equal(t, scenery.expected, actual)
})
@ -109,3 +109,19 @@ func TestGetConfigQuota(t *testing.T) {
assert.Equal(t, scenery.expected, quota)
}
}
func TestGetConfigLogs(t *testing.T) {
scenarios := []struct {
description string
expected string
}{
{"Default log config", `{"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}`},
}
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
quota := GetConfigString("logConfig")
assert.Equal(t, scenery.expected, quota)
}
}

View File

@ -18,9 +18,9 @@ import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/casdoor/casdoor/form"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -32,45 +32,9 @@ const (
ResponseTypeIdToken = "id_token"
ResponseTypeSaml = "saml"
ResponseTypeCas = "cas"
ResponseTypeDevice = "device"
)
type RequestForm 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"`
Application string `json:"application"`
Provider string `json:"provider"`
Code string `json:"code"`
State string `json:"state"`
RedirectUri string `json:"redirectUri"`
Method string `json:"method"`
EmailCode string `json:"emailCode"`
PhoneCode string `json:"phoneCode"`
PhonePrefix string `json:"phonePrefix"`
AutoSignin bool `json:"autoSignin"`
RelayState string `json:"relayState"`
SamlRequest string `json:"samlRequest"`
SamlResponse string `json:"samlResponse"`
CaptchaType string `json:"captchaType"`
CaptchaToken string `json:"captchaToken"`
ClientSecret string `json:"clientSecret"`
}
type Response struct {
Status string `json:"status"`
Msg string `json:"msg"`
@ -78,9 +42,12 @@ type Response struct {
Name string `json:"name"`
Data interface{} `json:"data"`
Data2 interface{} `json:"data2"`
Data3 interface{} `json:"data3"`
}
type Captcha struct {
Owner string `json:"owner"`
Name string `json:"name"`
Type string `json:"type"`
AppKey string `json:"appKey"`
Scene string `json:"scene"`
@ -93,6 +60,17 @@ type Captcha struct {
SubType string `json:"subType"`
}
// this API is used by "Api URL" of Flarum's FoF Passport plugin
// https://github.com/FriendsOfFlarum/passport
type LaravelResponse struct {
Id string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
EmailVerifiedAt string `json:"email_verified_at"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// Signup
// @Tag Login API
// @Title Signup
@ -103,94 +81,160 @@ type Captcha struct {
// @router /signup [post]
func (c *ApiController) Signup() {
if c.GetSessionUsername() != "" {
c.ResponseError(c.T("account:Please sign out first before signing up"), c.GetSessionUsername())
c.ResponseError(c.T("account:Please sign out first"), c.GetSessionUsername())
return
}
var form RequestForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
var authForm form.AuthForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
if err != nil {
c.ResponseError(err.Error())
return
}
application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application))
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return
}
if !application.EnableSignUp {
c.ResponseError(c.T("account:The application does not allow to sign up new account"))
return
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", form.Organization))
msg := object.CheckUserSignup(application, organization, form.Username, form.Password, form.Name, form.FirstName, form.LastName, form.Email, form.Phone, form.Affiliation, c.GetAcceptLanguage())
organization, err := object.GetOrganization(util.GetId("admin", authForm.Organization))
if err != nil {
c.ResponseError(c.T(err.Error()))
return
}
if organization == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The organization: %s does not exist"), authForm.Organization))
return
}
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
err = object.CheckEntryIp(clientIp, nil, application, organization, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
msg := object.CheckUserSignup(application, organization, &authForm, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
return
}
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && form.Email != "" {
checkResult := object.CheckVerificationCode(form.Email, form.EmailCode, c.GetAcceptLanguage())
if len(checkResult) != 0 {
c.ResponseError(c.T("account:Email: %s"), checkResult)
invitation, msg := object.CheckInvitationCode(application, organization, &authForm, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
return
}
invitationName := ""
if invitation != nil {
invitationName = invitation.Name
}
userEmailVerified := false
if application.IsSignupItemVisible("Email") && application.GetSignupItemRule("Email") != "No verification" && authForm.Email != "" {
var checkResult *object.VerifyResult
checkResult, err = object.CheckVerificationCode(authForm.Email, authForm.EmailCode, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(c.T(err.Error()))
return
}
if checkResult.Code != object.VerificationSuccess {
c.ResponseError(checkResult.Msg)
return
}
userEmailVerified = true
}
var checkPhone string
if application.IsSignupItemVisible("Phone") && form.Phone != "" {
checkPhone = fmt.Sprintf("+%s%s", form.PhonePrefix, form.Phone)
checkResult := object.CheckVerificationCode(checkPhone, form.PhoneCode, c.GetAcceptLanguage())
if len(checkResult) != 0 {
c.ResponseError(c.T("account:Phone: %s"), checkResult)
if application.IsSignupItemVisible("Phone") && application.GetSignupItemRule("Phone") != "No verification" && authForm.Phone != "" {
checkPhone, _ = util.GetE164Number(authForm.Phone, authForm.CountryCode)
var checkResult *object.VerifyResult
checkResult, err = object.CheckVerificationCode(checkPhone, authForm.PhoneCode, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(c.T(err.Error()))
return
}
if checkResult.Code != object.VerificationSuccess {
c.ResponseError(checkResult.Msg)
return
}
}
id := util.GenerateId()
if application.GetSignupItemRule("ID") == "Incremental" {
lastUser := object.GetLastUser(form.Organization)
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 := form.Username
username := authForm.Username
if !application.IsSignupItemVisible("Username") {
username = id
if organization.UseEmailAsUsername && application.IsSignupItemVisible("Email") {
username = authForm.Email
} else {
username = id
}
}
initScore, err := getInitScore(organization)
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: form.Organization,
Owner: authForm.Organization,
Name: username,
CreatedTime: util.GetCurrentTime(),
Id: id,
Type: "normal-user",
Password: form.Password,
DisplayName: form.Name,
Type: userType,
Password: authForm.Password,
DisplayName: authForm.Name,
Gender: authForm.Gender,
Bio: authForm.Bio,
Tag: authForm.Tag,
Education: authForm.Education,
Avatar: organization.DefaultAvatar,
Email: form.Email,
Phone: form.Phone,
Email: authForm.Email,
Phone: authForm.Phone,
CountryCode: authForm.CountryCode,
Address: []string{},
Affiliation: form.Affiliation,
IdCard: form.IdCard,
Region: form.Region,
Affiliation: authForm.Affiliation,
IdCard: authForm.IdCard,
Region: authForm.Region,
Score: initScore,
IsAdmin: false,
IsGlobalAdmin: false,
IsForbidden: false,
IsDeleted: false,
SignupApplication: application.Name,
Properties: map[string]string{},
Karma: 0,
Invitation: invitationName,
InvitationCode: authForm.InvitationCode,
EmailVerified: userEmailVerified,
}
if len(organization.Tags) > 0 {
@ -201,33 +245,70 @@ func (c *ApiController) Signup() {
}
if application.GetSignupItemRule("Display name") == "First, last" {
if form.FirstName != "" || form.LastName != "" {
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
user.FirstName = form.FirstName
user.LastName = form.LastName
if authForm.FirstName != "" || authForm.LastName != "" {
user.DisplayName = fmt.Sprintf("%s %s", authForm.FirstName, authForm.LastName)
user.FirstName = authForm.FirstName
user.LastName = authForm.LastName
}
}
affected := object.AddUser(user)
if !affected {
c.ResponseError(c.T("account:Invalid information"), util.StructToJson(user))
if invitation != nil && invitation.SignupGroup != "" {
user.Groups = []string{invitation.SignupGroup}
}
if application.DefaultGroup != "" && user.Groups == nil {
user.Groups = []string{application.DefaultGroup}
}
affected, err := object.AddUser(user, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
object.AddUserToOriginalDatabase(user)
if !affected {
c.ResponseError(c.T("account:Failed to add user"), util.StructToJson(user))
return
}
if application.HasPromptPage() {
err = object.AddUserToOriginalDatabase(user)
if err != nil {
c.ResponseError(err.Error())
return
}
if invitation != nil {
invitation.UsedCount += 1
_, err := object.UpdateInvitation(invitation.GetId(), invitation, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
}
if application.HasPromptPage() && user.Type == "normal-user" {
// The prompt page needs the user to be signed in
c.SetSessionUsername(user.GetId())
}
object.DisableVerificationCode(form.Email)
object.DisableVerificationCode(checkPhone)
if authForm.Email != "" {
err = object.DisableVerificationCode(authForm.Email)
if err != nil {
c.ResponseError(err.Error())
return
}
}
record := object.NewRecord(c.Ctx)
record.Organization = application.Organization
record.User = user.Name
util.SafeGoroutine(func() { object.AddRecord(record) })
if checkPhone != "" {
err = object.DisableVerificationCode(checkPhone)
if err != nil {
c.ResponseError(err.Error())
return
}
}
c.Ctx.Input.SetParam("recordUserId", user.GetId())
c.Ctx.Input.SetParam("recordSignup", "true")
userId := user.GetId()
util.LogInfo(c.Ctx, "API: [%s] is signed up as new user", userId)
@ -243,18 +324,31 @@ func (c *ApiController) Signup() {
// @Param post_logout_redirect_uri query string false "post_logout_redirect_uri"
// @Param state query string false "state"
// @Success 200 {object} controllers.Response The Response object
// @router /logout [get,post]
// @router /logout [post]
func (c *ApiController) Logout() {
user := c.GetSessionUsername()
// https://openid.net/specs/openid-connect-rpinitiated-1_0-final.html
accessToken := c.Input().Get("id_token_hint")
redirectUri := c.Input().Get("post_logout_redirect_uri")
state := c.Input().Get("state")
user := c.GetSessionUsername()
if accessToken == "" && redirectUri == "" {
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
if user == "" {
c.ResponseOk()
return
}
c.ClearUserSession()
object.DeleteSessionId(user, c.Ctx.Input.CruSession.SessionID())
c.ClearTokenSession()
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())
return
}
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
@ -265,40 +359,66 @@ func (c *ApiController) Logout() {
c.ResponseOk(user, application.HomepageUrl)
return
} else {
if redirectUri == "" {
c.ResponseError(c.T("general:Missing parameter") + ": post_logout_redirect_uri")
return
}
// "post_logout_redirect_uri" has been made optional, see: https://github.com/casdoor/casdoor/issues/2151
// if redirectUri == "" {
// c.ResponseError(c.T("general:Missing parameter") + ": post_logout_redirect_uri")
// return
// }
if accessToken == "" {
c.ResponseError(c.T("general:Missing parameter") + ": id_token_hint")
return
}
affected, application, token := object.ExpireTokenByAccessToken(accessToken)
if !affected {
_, application, token, err := object.ExpireTokenByAccessToken(accessToken)
if err != nil {
c.ResponseError(err.Error())
return
}
if token == nil {
c.ResponseError(c.T("token:Token not found, invalid accessToken"))
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist")), token.Application)
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()
object.DeleteSessionId(user, c.Ctx.Input.CruSession.SessionID())
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
c.ClearUserSession()
c.ClearTokenSession()
// TODO https://github.com/casdoor/casdoor/pull/1494#discussion_r1095675265
owner, username := util.GetOwnerAndNameFromId(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))
_, 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) {
redirectUrl := redirectUri
if state != "" {
if strings.Contains(redirectUri, "?") {
redirectUrl = fmt.Sprintf("%s&state=%s", strings.TrimSuffix(redirectUri, "/"), state)
} else {
redirectUrl = fmt.Sprintf("%s?state=%s", strings.TrimSuffix(redirectUri, "/"), state)
}
}
c.Ctx.Redirect(http.StatusFound, redirectUrl)
} else {
c.ResponseError(fmt.Sprintf(c.T("token:Redirect URI: %s doesn't exist in the allowed Redirect URI list"), redirectUri))
return
}
}
}
}
@ -309,6 +429,7 @@ func (c *ApiController) Logout() {
// @Success 200 {object} controllers.Response The Response object
// @router /get-account [get]
func (c *ApiController) GetAccount() {
var err error
user, ok := c.RequireSignedInUser()
if !ok {
return
@ -316,20 +437,58 @@ func (c *ApiController) GetAccount() {
managedAccounts := c.Input().Get("managedAccounts")
if managedAccounts == "1" {
user = object.ExtendManagedAccountsWithUser(user)
user, err = object.ExtendManagedAccountsWithUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
}
object.ExtendUserWithRolesAndPermissions(user)
err = object.ExtendUserWithRolesAndPermissions(user)
if err != nil {
c.ResponseError(err.Error())
return
}
user.Permissions = object.GetMaskedPermissions(user.Permissions)
user.Roles = object.GetMaskedRoles(user.Roles)
if user != nil {
user.Permissions = object.GetMaskedPermissions(user.Permissions)
user.Roles = object.GetMaskedRoles(user.Roles)
user.MultiFactorAuths = object.GetAllMfaProps(user, true)
}
organization, err := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
if err != nil {
c.ResponseError(err.Error())
return
}
isAdminOrSelf := c.IsAdminOrSelf(user)
u, err := object.GetMaskedUser(user, isAdminOrSelf)
if err != nil {
c.ResponseError(err.Error())
return
}
if organization != nil && len(organization.CountryCodes) == 1 && u != nil && u.CountryCode == "" {
u.CountryCode = organization.CountryCodes[0]
}
accessToken := c.GetSessionToken()
if accessToken == "" {
accessToken, err = object.GetAccessTokenByUser(user, c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSessionToken(accessToken)
}
u.AccessToken = accessToken
organization := object.GetMaskedOrganization(object.GetOrganizationByUser(user))
resp := Response{
Status: "ok",
Sub: user.Id,
Name: user.Name,
Data: object.GetMaskedUser(user),
Data: u,
Data2: organization,
}
c.Data["json"] = resp
@ -351,16 +510,48 @@ func (c *ApiController) GetUserinfo() {
scope, aud := c.GetSessionOidc()
host := c.Ctx.Request.Host
userInfo := object.GetUserInfo(user, scope, aud, host)
userInfo, err := object.GetUserInfo(user, scope, aud, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = userInfo
c.ServeJSON()
}
// GetUserinfo2
// LaravelResponse
// @Title UserInfo2
// @Tag Account API
// @Description return Laravel compatible user information according to OAuth 2.0
// @Success 200 {object} controllers.LaravelResponse The Response object
// @router /user [get]
func (c *ApiController) GetUserinfo2() {
user, ok := c.RequireSignedInUser()
if !ok {
return
}
response := LaravelResponse{
Id: user.Id,
Name: user.Name,
Email: user.Email,
EmailVerifiedAt: user.CreatedTime,
CreatedAt: user.CreatedTime,
UpdatedAt: user.UpdatedTime,
}
c.Data["json"] = response
c.ServeJSON()
}
// GetCaptcha ...
// @Tag Login API
// @Title GetCaptcha
// @router /api/get-captcha [get]
// @router /get-captcha [get]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) GetCaptcha() {
applicationId := c.Input().Get("applicationId")
isCurrentProvider := c.Input().Get("isCurrentProvider")
@ -373,15 +564,22 @@ func (c *ApiController) GetCaptcha() {
if captchaProvider != nil {
if captchaProvider.Type == "Default" {
id, img := object.GetCaptcha()
c.ResponseOk(Captcha{Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
id, img, err := object.GetCaptcha()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(Captcha{Owner: captchaProvider.Owner, Name: captchaProvider.Name, Type: captchaProvider.Type, CaptchaId: id, CaptchaImage: img})
return
} else if captchaProvider.Type != "" {
c.ResponseOk(Captcha{
Owner: captchaProvider.Owner,
Name: captchaProvider.Name,
Type: captchaProvider.Type,
SubType: captchaProvider.SubType,
ClientId: captchaProvider.ClientId,
ClientSecret: captchaProvider.ClientSecret,
ClientSecret: "***",
ClientId2: captchaProvider.ClientId2,
ClientSecret2: captchaProvider.ClientSecret2,
})

145
controllers/adapter.go Normal file
View File

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

View File

@ -40,21 +40,35 @@ func (c *ApiController) GetApplications() {
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
var err error
if limit == "" || page == "" {
var applications []*object.Application
if organization == "" {
applications = object.GetApplications(owner)
applications, err = object.GetApplications(owner)
} else {
applications = object.GetOrganizationApplications(owner, organization)
applications, err = object.GetOrganizationApplications(owner, organization)
}
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedApplications(applications, userId))
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetApplicationCount(owner, field, value)))
applications := object.GetMaskedApplications(object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
count, err := object.GetApplicationCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
application, err := object.GetPaginationApplications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
applications := object.GetMaskedApplications(application, userId)
c.ResponseOk(applications, paginator.Nums())
}
}
@ -63,35 +77,77 @@ func (c *ApiController) GetApplications() {
// @Title GetApplication
// @Tag Application API
// @Description get the detail of an application
// @Param id query string true "The id of the application."
// @Param id query string true "The id ( owner/name ) of the application."
// @Success 200 {object} object.Application The Response object
// @router /get-application [get]
func (c *ApiController) GetApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
c.Data["json"] = object.GetMaskedApplication(object.GetApplication(id), userId)
c.ServeJSON()
application, err := object.GetApplication(id)
if err != nil {
c.ResponseError(err.Error())
return
}
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
}
}
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
object.CheckEntryIp(clientIp, nil, application, nil, c.GetAcceptLanguage())
c.ResponseOk(object.GetMaskedApplication(application, userId))
}
// GetUserApplication
// @Title GetUserApplication
// @Tag Application API
// @Description get the detail of the user's application
// @Param id query string true "The id of the user"
// @Param id query string true "The id ( owner/name ) of the user"
// @Success 200 {object} object.Application The Response object
// @router /get-user-application [get]
func (c *ApiController) GetUserApplication() {
userId := c.GetSessionUsername()
id := c.Input().Get("id")
user := object.GetUser(id)
user, err := object.GetUser(id)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
return
}
c.Data["json"] = object.GetMaskedApplication(object.GetApplicationByUser(user), userId)
c.ServeJSON()
application, err := object.GetApplicationByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The organization: %s should have one application at least"), user.Owner))
return
}
c.ResponseOk(object.GetMaskedApplication(application, userId))
}
// GetOrganizationApplications
@ -118,14 +174,42 @@ func (c *ApiController) GetOrganizationApplications() {
}
if limit == "" || page == "" {
var applications []*object.Application
applications = object.GetOrganizationApplications(owner, organization)
c.Data["json"] = object.GetMaskedApplications(applications, userId)
c.ServeJSON()
applications, err := object.GetOrganizationApplications(owner, organization)
if err != nil {
c.ResponseError(err.Error())
return
}
applications, err = object.GetAllowedApplications(applications, userId, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedApplications(applications, userId))
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationApplicationCount(owner, organization, field, value)))
applications := object.GetMaskedApplications(object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder), userId)
count, err := object.GetOrganizationApplicationCount(owner, organization, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
applications, err := object.GetPaginationOrganizationApplications(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
applications, err = object.GetAllowedApplications(applications, userId, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
applications = object.GetMaskedApplications(applications, userId)
c.ResponseOk(applications, paginator.Nums())
}
}
@ -134,7 +218,7 @@ func (c *ApiController) GetOrganizationApplications() {
// @Title UpdateApplication
// @Tag Application API
// @Description update an application
// @Param id query string true "The id of the application"
// @Param id query string true "The id ( owner/name ) of the application"
// @Param body body object.Application true "The details of the application"
// @Success 200 {object} controllers.Response The Response object
// @router /update-application [post]
@ -148,6 +232,11 @@ func (c *ApiController) UpdateApplication() {
return
}
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateApplication(id, &application))
c.ServeJSON()
}
@ -167,8 +256,18 @@ func (c *ApiController) AddApplication() {
return
}
count := object.GetApplicationCount("", "", "")
if err := checkQuotaForApplication(count); err != nil {
count, err := object.GetApplicationCount("", "", "")
if err != nil {
c.ResponseError(err.Error())
return
}
if err := checkQuotaForApplication(int(count)); err != nil {
c.ResponseError(err.Error())
return
}
if err = object.CheckIpWhitelist(application.IpWhitelist, c.GetAcceptLanguage()); err != nil {
c.ResponseError(err.Error())
return
}

File diff suppressed because it is too large Load Diff

View File

@ -41,18 +41,65 @@ type SessionData struct {
}
func (c *ApiController) IsGlobalAdmin() bool {
username := c.GetSessionUsername()
if strings.HasPrefix(username, "app/") {
// e.g., "app/app-casnode"
return true
}
isGlobalAdmin, _ := c.isGlobalAdmin()
user := object.GetUser(username)
if user == nil {
return isGlobalAdmin
}
func (c *ApiController) IsAdmin() bool {
isGlobalAdmin, user := c.isGlobalAdmin()
if !isGlobalAdmin && user == nil {
return false
}
return user.Owner == "built-in" || user.IsGlobalAdmin
return isGlobalAdmin || user.IsAdmin
}
func (c *ApiController) IsAdminOrSelf(user2 *object.User) bool {
isGlobalAdmin, user := c.isGlobalAdmin()
if isGlobalAdmin || (user != nil && user.IsAdmin) {
return true
}
if user == nil || user2 == nil {
return false
}
if user.Owner == user2.Owner && user.Name == user2.Name {
return true
}
return false
}
func (c *ApiController) isGlobalAdmin() (bool, *object.User) {
username := c.GetSessionUsername()
if object.IsAppUser(username) {
// e.g., "app/app-casnode"
return true, nil
}
user := c.getCurrentUser()
if user == nil {
return false, nil
}
return user.IsGlobalAdmin(), user
}
func (c *ApiController) getCurrentUser() *object.User {
var user *object.User
var err error
userId := c.GetSessionUsername()
if userId == "" {
user = nil
} else {
user, err = object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return nil
}
}
return user
}
// GetSessionUsername ...
@ -75,12 +122,26 @@ func (c *ApiController) GetSessionUsername() string {
return user.(string)
}
func (c *ApiController) GetSessionToken() string {
accessToken := c.GetSession("accessToken")
if accessToken == nil {
return ""
}
return accessToken.(string)
}
func (c *ApiController) GetSessionApplication() *object.Application {
clientId := c.GetSession("aud")
if clientId == nil {
return nil
}
application := object.GetApplicationByClientId(clientId.(string))
application, err := object.GetApplicationByClientId(clientId.(string))
if err != nil {
c.ResponseError(err.Error())
return nil
}
return application
}
@ -89,6 +150,10 @@ func (c *ApiController) ClearUserSession() {
c.SetSessionData(nil)
}
func (c *ApiController) ClearTokenSession() {
c.SetSessionToken("")
}
func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData()
if sessionData != nil &&
@ -115,6 +180,10 @@ func (c *ApiController) SetSessionUsername(user string) {
c.SetSession("username", user)
}
func (c *ApiController) SetSessionToken(accessToken string) {
c.SetSession("accessToken", accessToken)
}
// GetSessionData ...
func (c *ApiController) GetSessionData() *SessionData {
session := c.GetSession("SessionData")
@ -142,8 +211,30 @@ func (c *ApiController) SetSessionData(s *SessionData) {
c.SetSession("SessionData", util.StructToJson(s))
}
func wrapActionResponse(affected bool) *Response {
if affected {
func (c *ApiController) setMfaUserSession(userId string) {
c.SetSession(object.MfaSessionUserId, userId)
}
func (c *ApiController) getMfaUserSession() string {
userId := c.Ctx.Input.CruSession.Get(object.MfaSessionUserId)
if userId == nil {
return ""
}
return userId.(string)
}
func (c *ApiController) setExpireForSession() {
timestamp := time.Now().Unix()
timestamp += 3600 * 24
c.SetSessionData(&SessionData{
ExpireTime: timestamp,
})
}
func wrapActionResponse(affected bool, e ...error) *Response {
if len(e) != 0 && e[0] != nil {
return &Response{Status: "error", Msg: e[0].Error()}
} else if affected {
return &Response{Status: "ok", Msg: "", Data: "Affected"}
} else {
return &Response{Status: "ok", Msg: "", Data: "Unaffected"}
@ -157,3 +248,14 @@ func wrapErrorResponse(err error) *Response {
return &Response{Status: "error", Msg: err.Error()}
}
}
func (c *ApiController) Finish() {
if strings.HasPrefix(c.Ctx.Input.URL(), "/api") {
startTime := c.Ctx.Input.GetData("startTime")
if startTime != nil {
latency := time.Since(startTime.(time.Time)).Milliseconds()
object.ApiLatency.WithLabelValues(c.Ctx.Input.URL(), c.Ctx.Input.Method()).Observe(float64(latency))
}
}
c.Controller.Finish()
}

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,19 +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() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
if !strings.HasPrefix(ticket, "PT") {
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
}
c.CasP3ServiceAndProxyValidate()
// 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) CasP3ServiceAndProxyValidate() {
func (c *RootController) CasP3ServiceValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
if !strings.HasPrefix(ticket, "ST") {
c.sendCasAuthenticationResponseErr(InvalidTicket, fmt.Sprintf("Ticket %s not recognized", ticket), format)
}
c.CasP3ProxyValidate()
}
func (c *RootController) CasP3ProxyValidate() {
ticket := c.Input().Get("ticket")
format := c.Input().Get("format")
service := c.Input().Get("service")
@ -91,7 +102,7 @@ func (c *RootController) CasP3ServiceAndProxyValidate() {
// find the token
if ok {
// check whether service is the one for which we previously issued token
if strings.HasPrefix(service, issuedService) {
if strings.HasPrefix(service, issuedService) || strings.HasPrefix(queryUnescape(service), issuedService) {
serviceResponse.Success = response
} else {
// service not match
@ -110,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)
@ -258,7 +271,6 @@ func (c *RootController) sendCasAuthenticationResponseErr(code, msg, format stri
Message: msg,
},
}
if format == "json" {
c.Data["json"] = serviceResponse
c.ServeJSON()

View File

@ -1,157 +0,0 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/beego/beego/utils/pagination"
xormadapter "github.com/casbin/xorm-adapter/v3"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func (c *ApiController) GetCasbinAdapters() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
adapters := object.GetCasbinAdapters(owner)
c.ResponseOk(adapters)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCasbinAdapterCount(owner, field, value)))
adapters := object.GetPaginationCasbinAdapters(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
c.ResponseOk(adapters, paginator.Nums())
}
}
func (c *ApiController) GetCasbinAdapter() {
id := c.Input().Get("id")
adapter := object.GetCasbinAdapter(id)
c.ResponseOk(adapter)
}
func (c *ApiController) UpdateCasbinAdapter() {
id := c.Input().Get("id")
var casbinAdapter object.CasbinAdapter
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateCasbinAdapter(id, &casbinAdapter))
c.ServeJSON()
}
func (c *ApiController) AddCasbinAdapter() {
var casbinAdapter object.CasbinAdapter
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddCasbinAdapter(&casbinAdapter))
c.ServeJSON()
}
func (c *ApiController) DeleteCasbinAdapter() {
var casbinAdapter object.CasbinAdapter
err := json.Unmarshal(c.Ctx.Input.RequestBody, &casbinAdapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteCasbinAdapter(&casbinAdapter))
c.ServeJSON()
}
func (c *ApiController) SyncPolicies() {
id := c.Input().Get("id")
adapter := object.GetCasbinAdapter(id)
policies, err := object.SyncPolicies(adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(policies)
}
func (c *ApiController) UpdatePolicy() {
id := c.Input().Get("id")
adapter := object.GetCasbinAdapter(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(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 := object.GetCasbinAdapter(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(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 := object.GetCasbinAdapter(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(util.CasbinToSlice(policy), adapter)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}

329
controllers/casbin_api.go Normal file
View File

@ -0,0 +1,329 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"fmt"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// Enforce
// @Title Enforce
// @Tag Enforcer API
// @Description Call Casbin Enforce API
// @Param body body []string true "Casbin request"
// @Param permissionId query string false "permission id"
// @Param modelId query string false "model id"
// @Param resourceId query string false "resource id"
// @Param owner query string false "owner"
// @Success 200 {object} controllers.Response The Response object
// @router /enforce [post]
func (c *ApiController) Enforce() {
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
resourceId := c.Input().Get("resourceId")
enforcerId := c.Input().Get("enforcerId")
owner := c.Input().Get("owner")
if len(c.Ctx.Input.RequestBody) == 0 {
c.ResponseError("The request body should not be empty")
return
}
var request []string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &request)
if err != nil {
c.ResponseError(err.Error())
return
}
if enforcerId != "" {
enforcer, err := object.GetInitializedEnforcer(enforcerId)
if err != nil {
c.ResponseError(err.Error())
return
}
res := []bool{}
keyRes := []string{}
// type transformation
interfaceRequest := util.StringToInterfaceArray(request)
enforceResult, err := enforcer.Enforce(interfaceRequest...)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
c.ResponseOk(res, keyRes)
return
}
if permissionId != "" {
permission, err := object.GetPermission(permissionId)
if err != nil {
c.ResponseError(err.Error())
return
}
if permission == nil {
c.ResponseError(fmt.Sprintf(c.T("permission:The permission: \"%s\" doesn't exist"), permissionId))
return
}
res := []bool{}
keyRes := []string{}
enforceResult, err := object.Enforce(permission, request)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
keyRes = append(keyRes, permission.GetModelAndAdapter())
c.ResponseOk(res, keyRes)
return
}
permissions := []*object.Permission{}
if modelId != "" {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions, err = object.GetPermissionsByModel(owner, modelName)
if err != nil {
c.ResponseError(err.Error())
return
}
} else if resourceId != "" {
permissions, err = object.GetPermissionsByResource(resourceId)
if err != nil {
c.ResponseError(err.Error())
return
}
} else if owner != "" {
permissions, err = object.GetPermissions(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
}
res := []bool{}
keyRes := []string{}
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for key, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0])
if err != nil {
c.ResponseError(err.Error())
return
}
enforceResult, err := object.Enforce(firstPermission, request, permissionIds...)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
keyRes = append(keyRes, key)
}
c.ResponseOk(res, keyRes)
}
// BatchEnforce
// @Title BatchEnforce
// @Tag Enforcer API
// @Description Call Casbin BatchEnforce API
// @Param body body []string true "array of casbin requests"
// @Param permissionId query string false "permission id"
// @Param modelId query string false "model id"
// @Param owner query string false "owner"
// @Success 200 {object} controllers.Response The Response object
// @router /batch-enforce [post]
func (c *ApiController) BatchEnforce() {
permissionId := c.Input().Get("permissionId")
modelId := c.Input().Get("modelId")
enforcerId := c.Input().Get("enforcerId")
owner := c.Input().Get("owner")
var requests [][]string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &requests)
if err != nil {
c.ResponseError(err.Error())
return
}
if enforcerId != "" {
enforcer, err := object.GetInitializedEnforcer(enforcerId)
if err != nil {
c.ResponseError(err.Error())
return
}
res := [][]bool{}
keyRes := []string{}
// type transformation
interfaceRequests := util.StringToInterfaceArray2d(requests)
enforceResult, err := enforcer.BatchEnforce(interfaceRequests)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
keyRes = append(keyRes, enforcer.GetModelAndAdapter())
c.ResponseOk(res, keyRes)
return
}
if permissionId != "" {
permission, err := object.GetPermission(permissionId)
if err != nil {
c.ResponseError(err.Error())
return
}
if permission == nil {
c.ResponseError(fmt.Sprintf(c.T("permission:The permission: \"%s\" doesn't exist"), permissionId))
return
}
res := [][]bool{}
keyRes := []string{}
enforceResult, err := object.BatchEnforce(permission, requests)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
keyRes = append(keyRes, permission.GetModelAndAdapter())
c.ResponseOk(res, keyRes)
return
}
permissions := []*object.Permission{}
if modelId != "" {
owner, modelName := util.GetOwnerAndNameFromId(modelId)
permissions, err = object.GetPermissionsByModel(owner, modelName)
if err != nil {
c.ResponseError(err.Error())
return
}
} else if owner != "" {
permissions, err = object.GetPermissions(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
c.ResponseError(c.T("general:Missing parameter"))
return
}
res := [][]bool{}
keyRes := []string{}
listPermissionIdMap := object.GroupPermissionsByModelAdapter(permissions)
for _, permissionIds := range listPermissionIdMap {
firstPermission, err := object.GetPermission(permissionIds[0])
if err != nil {
c.ResponseError(err.Error())
return
}
enforceResult, err := object.BatchEnforce(firstPermission, requests, permissionIds...)
if err != nil {
c.ResponseError(err.Error())
return
}
res = append(res, enforceResult)
keyRes = append(keyRes, firstPermission.GetModelAndAdapter())
}
c.ResponseOk(res, keyRes)
}
func (c *ApiController) GetAllObjects() {
userId := c.Input().Get("userId")
if userId == "" {
userId = c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
}
objects, err := object.GetAllObjects(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(objects)
}
func (c *ApiController) GetAllActions() {
userId := c.Input().Get("userId")
if userId == "" {
userId = c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
}
actions, err := object.GetAllActions(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(actions)
}
func (c *ApiController) GetAllRoles() {
userId := c.Input().Get("userId")
if userId == "" {
userId = c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
}
roles, err := object.GetAllRoles(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(roles)
}

View File

@ -0,0 +1,247 @@
// Copyright 2024 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 (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"os"
"os/exec"
"sort"
"strings"
"sync"
"time"
)
type CLIVersionInfo struct {
Version string
BinaryPath string
BinaryTime time.Time
}
var (
cliVersionCache = make(map[string]*CLIVersionInfo)
cliVersionMutex sync.RWMutex
)
// getCLIVersion
// @Title getCLIVersion
// @Description Get CLI version with cache mechanism
// @Param language string The language of CLI (go/java/rust etc.)
// @Return string The version string of CLI
// @Return error Error if CLI execution fails
func getCLIVersion(language string) (string, error) {
binaryName := fmt.Sprintf("casbin-%s-cli", language)
binaryPath, err := exec.LookPath(binaryName)
if err != nil {
return "", fmt.Errorf("executable file not found: %v", err)
}
fileInfo, err := os.Stat(binaryPath)
if err != nil {
return "", fmt.Errorf("failed to get binary info: %v", err)
}
cliVersionMutex.RLock()
if info, exists := cliVersionCache[language]; exists {
if info.BinaryPath == binaryPath && info.BinaryTime == fileInfo.ModTime() {
cliVersionMutex.RUnlock()
return info.Version, nil
}
}
cliVersionMutex.RUnlock()
cmd := exec.Command(binaryName, "--version")
output, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("failed to get CLI version: %v", err)
}
version := strings.TrimSpace(string(output))
cliVersionMutex.Lock()
cliVersionCache[language] = &CLIVersionInfo{
Version: version,
BinaryPath: binaryPath,
BinaryTime: fileInfo.ModTime(),
}
cliVersionMutex.Unlock()
return version, nil
}
func processArgsToTempFiles(args []string) ([]string, []string, error) {
tempFiles := []string{}
newArgs := []string{}
for i := 0; i < len(args); i++ {
if (args[i] == "-m" || args[i] == "-p") && i+1 < len(args) {
pattern := fmt.Sprintf("casbin_temp_%s_*.conf", args[i])
tempFile, err := os.CreateTemp("", pattern)
if err != nil {
return nil, nil, fmt.Errorf("failed to create temp file: %v", err)
}
_, err = tempFile.WriteString(args[i+1])
if err != nil {
tempFile.Close()
return nil, nil, fmt.Errorf("failed to write to temp file: %v", err)
}
tempFile.Close()
tempFiles = append(tempFiles, tempFile.Name())
newArgs = append(newArgs, args[i], tempFile.Name())
i++
} else {
newArgs = append(newArgs, args[i])
}
}
return tempFiles, newArgs, nil
}
// RunCasbinCommand
// @Title RunCasbinCommand
// @Tag Enforcer API
// @Description Call Casbin CLI commands
// @Success 200 {object} controllers.Response The Response object
// @router /run-casbin-command [get]
func (c *ApiController) RunCasbinCommand() {
if err := validateIdentifier(c); err != nil {
c.ResponseError(err.Error())
return
}
language := c.Input().Get("language")
argString := c.Input().Get("args")
if language == "" {
language = "go"
}
// use "casbin-go-cli" by default, can be also "casbin-java-cli", "casbin-node-cli", etc.
// the pre-built binary of "casbin-go-cli" can be found at: https://github.com/casbin/casbin-go-cli/releases
binaryName := fmt.Sprintf("casbin-%s-cli", language)
_, err := exec.LookPath(binaryName)
if err != nil {
c.ResponseError(fmt.Sprintf("executable file: %s not found in PATH", binaryName))
return
}
// RBAC model & policy example:
// https://door.casdoor.com/api/run-casbin-command?language=go&args=["enforce", "-m", "[request_definition]\nr = sub, obj, act\n\n[policy_definition]\np = sub, obj, act\n\n[role_definition]\ng = _, _\n\n[policy_effect]\ne = some(where (p.eft == allow))\n\n[matchers]\nm = g(r.sub, p.sub) %26%26 r.obj == p.obj %26%26 r.act == p.act", "-p", "p, alice, data1, read\np, bob, data2, write\np, data2_admin, data2, read\np, data2_admin, data2, write\ng, alice, data2_admin", "alice", "data1", "read"]
// Casbin CLI usage:
// https://github.com/jcasbin/casbin-java-cli?tab=readme-ov-file#get-started
var args []string
err = json.Unmarshal([]byte(argString), &args)
if err != nil {
c.ResponseError(err.Error())
return
}
if len(args) > 0 && args[0] == "--version" {
version, err := getCLIVersion(language)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(version)
return
}
tempFiles, processedArgs, err := processArgsToTempFiles(args)
defer func() {
for _, file := range tempFiles {
os.Remove(file)
}
}()
if err != nil {
c.ResponseError(err.Error())
return
}
command := exec.Command(binaryName, processedArgs...)
outputBytes, err := command.CombinedOutput()
if err != nil {
errorString := err.Error()
if outputBytes != nil {
output := string(outputBytes)
errorString = fmt.Sprintf("%s, error: %s", output, err.Error())
}
c.ResponseError(errorString)
return
}
output := string(outputBytes)
output = strings.TrimSuffix(output, "\n")
c.ResponseOk(output)
}
// validateIdentifier
// @Title validateIdentifier
// @Description Validate the request hash and timestamp
// @Param hash string The SHA-256 hash string
// @Return error Returns error if validation fails, nil if successful
func validateIdentifier(c *ApiController) error {
language := c.Input().Get("language")
args := c.Input().Get("args")
hash := c.Input().Get("m")
timestamp := c.Input().Get("t")
if hash == "" || timestamp == "" || language == "" || args == "" {
return fmt.Errorf("invalid identifier")
}
requestTime, err := time.Parse(time.RFC3339, timestamp)
if err != nil {
return fmt.Errorf("invalid identifier")
}
timeDiff := time.Since(requestTime)
if timeDiff > 5*time.Minute || timeDiff < -5*time.Minute {
return fmt.Errorf("invalid identifier")
}
params := map[string]string{
"language": language,
"args": args,
}
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
var paramParts []string
for _, k := range keys {
paramParts = append(paramParts, fmt.Sprintf("%s=%s", k, params[k]))
}
paramString := strings.Join(paramParts, "&")
version := "casbin-editor-v1"
rawString := fmt.Sprintf("%s|%s|%s", version, timestamp, paramString)
hasher := sha256.New()
hasher.Write([]byte(rawString))
calculatedHash := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
if calculatedHash != strings.ToLower(hash) {
return fmt.Errorf("invalid identifier")
}
return nil
}

View File

@ -37,13 +37,71 @@ func (c *ApiController) GetCerts() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedCerts(object.GetCerts(owner))
c.ServeJSON()
certs, err := object.GetMaskedCerts(object.GetCerts(owner))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(certs)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetCertCount(owner, field, value)))
certs := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
count, err := object.GetCertCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
certs, err := object.GetMaskedCerts(object.GetPaginationCerts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(certs, paginator.Nums())
}
}
// GetGlobalCerts
// @Title GetGlobalCerts
// @Tag Cert API
// @Description get global certs
// @Success 200 {array} object.Cert The Response object
// @router /get-global-certs [get]
func (c *ApiController) GetGlobalCerts() {
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
certs, err := object.GetMaskedCerts(object.GetGlobalCerts())
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(certs)
} else {
limit := util.ParseInt(limit)
count, err := object.GetGlobalCertsCount(field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
certs, err := object.GetMaskedCerts(object.GetPaginationGlobalCerts(paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(certs, paginator.Nums())
}
}
@ -52,21 +110,25 @@ func (c *ApiController) GetCerts() {
// @Title GetCert
// @Tag Cert API
// @Description get cert
// @Param id query string true "The id of the cert"
// @Param id query string true "The id ( owner/name ) of the cert"
// @Success 200 {object} object.Cert The Response object
// @router /get-cert [get]
func (c *ApiController) GetCert() {
id := c.Input().Get("id")
cert, err := object.GetCert(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetMaskedCert(object.GetCert(id))
c.ServeJSON()
c.ResponseOk(object.GetMaskedCert(cert))
}
// UpdateCert
// @Title UpdateCert
// @Tag Cert API
// @Description update cert
// @Param id query string true "The id of the cert"
// @Param id query string true "The id ( owner/name ) of the cert"
// @Param body body object.Cert true "The details of the cert"
// @Success 200 {object} controllers.Response The Response object
// @router /update-cert [post]

View File

@ -0,0 +1,519 @@
package controllers
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/beego/beego"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util"
)
const (
javaCliRepo = "https://api.github.com/repos/jcasbin/casbin-java-cli/releases/latest"
goCliRepo = "https://api.github.com/repos/casbin/casbin-go-cli/releases/latest"
rustCliRepo = "https://api.github.com/repos/casbin-rs/casbin-rust-cli/releases/latest"
downloadFolder = "bin"
)
type ReleaseInfo struct {
TagName string `json:"tag_name"`
Assets []struct {
Name string `json:"name"`
URL string `json:"browser_download_url"`
} `json:"assets"`
}
// @Title getBinaryNames
// @Description Get binary names for different platforms and architectures
// @Success 200 {map[string]string} map[string]string "Binary names map"
func getBinaryNames() map[string]string {
const (
golang = "go"
java = "java"
rust = "rust"
)
arch := runtime.GOARCH
archMap := map[string]struct{ goArch, rustArch string }{
"amd64": {"x86_64", "x86_64"},
"arm64": {"arm64", "aarch64"},
}
archNames, ok := archMap[arch]
if !ok {
archNames = struct{ goArch, rustArch string }{arch, arch}
}
switch runtime.GOOS {
case "windows":
return map[string]string{
golang: fmt.Sprintf("casbin-go-cli_Windows_%s.zip", archNames.goArch),
java: "casbin-java-cli.jar",
rust: fmt.Sprintf("casbin-rust-cli-%s-pc-windows-gnu", archNames.rustArch),
}
case "darwin":
return map[string]string{
golang: fmt.Sprintf("casbin-go-cli_Darwin_%s.tar.gz", archNames.goArch),
java: "casbin-java-cli.jar",
rust: fmt.Sprintf("casbin-rust-cli-%s-apple-darwin", archNames.rustArch),
}
case "linux":
return map[string]string{
golang: fmt.Sprintf("casbin-go-cli_Linux_%s.tar.gz", archNames.goArch),
java: "casbin-java-cli.jar",
rust: fmt.Sprintf("casbin-rust-cli-%s-unknown-linux-gnu", archNames.rustArch),
}
default:
return nil
}
}
// @Title getFinalBinaryName
// @Description Get final binary name for specific language
// @Param lang string true "Language type (go/java/rust)"
// @Success 200 {string} string "Final binary name"
func getFinalBinaryName(lang string) string {
switch lang {
case "go":
if runtime.GOOS == "windows" {
return "casbin-go-cli.exe"
}
return "casbin-go-cli"
case "java":
return "casbin-java-cli.jar"
case "rust":
if runtime.GOOS == "windows" {
return "casbin-rust-cli.exe"
}
return "casbin-rust-cli"
default:
return ""
}
}
// @Title getLatestCLIURL
// @Description Get latest CLI download URL from GitHub
// @Param repoURL string true "GitHub repository URL"
// @Param language string true "Language type"
// @Success 200 {string} string "Download URL and version"
func getLatestCLIURL(repoURL string, language string) (string, string, error) {
client := proxy.GetHttpClient(repoURL)
resp, err := client.Get(repoURL)
if err != nil {
return "", "", fmt.Errorf("failed to fetch release info: %v", err)
}
defer resp.Body.Close()
var release ReleaseInfo
if err := json.NewDecoder(resp.Body).Decode(&release); err != nil {
return "", "", err
}
binaryNames := getBinaryNames()
if binaryNames == nil {
return "", "", fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
binaryName := binaryNames[language]
for _, asset := range release.Assets {
if asset.Name == binaryName {
return asset.URL, release.TagName, nil
}
}
return "", "", fmt.Errorf("no suitable binary found for OS: %s, language: %s", runtime.GOOS, language)
}
// @Title extractGoCliFile
// @Description Extract the Go CLI file
// @Param filePath string true "The file path"
// @Success 200 {string} string "The extracted file path"
// @router /extractGoCliFile [post]
func extractGoCliFile(filePath string) error {
tempDir := filepath.Join(downloadFolder, "temp")
if err := os.MkdirAll(tempDir, 0o755); err != nil {
return err
}
defer os.RemoveAll(tempDir)
if runtime.GOOS == "windows" {
if err := unzipFile(filePath, tempDir); err != nil {
return err
}
} else {
if err := untarFile(filePath, tempDir); err != nil {
return err
}
}
execName := "casbin-go-cli"
if runtime.GOOS == "windows" {
execName += ".exe"
}
var execPath string
err := filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
if info.Name() == execName {
execPath = path
return nil
}
return nil
})
if err != nil {
return err
}
finalPath := filepath.Join(downloadFolder, execName)
if err := os.Rename(execPath, finalPath); err != nil {
return err
}
return os.Remove(filePath)
}
// @Title unzipFile
// @Description Unzip the file
// @Param zipPath string true "The zip file path"
// @Param destDir string true "The destination directory"
// @Success 200 {string} string "The extracted file path"
// @router /unzipFile [post]
func unzipFile(zipPath, destDir string) error {
r, err := zip.OpenReader(zipPath)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
fpath := filepath.Join(destDir, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
outFile.Close()
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
// @Title untarFile
// @Description Untar the file
// @Param tarPath string true "The tar file path"
// @Param destDir string true "The destination directory"
// @Success 200 {string} string "The extracted file path"
// @router /untarFile [post]
func untarFile(tarPath, destDir string) error {
file, err := os.Open(tarPath)
if err != nil {
return err
}
defer file.Close()
gzr, err := gzip.NewReader(file)
if err != nil {
return err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
header, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
path := filepath.Join(destDir, header.Name)
switch header.Typeflag {
case tar.TypeDir:
if err := os.MkdirAll(path, 0o755); err != nil {
return err
}
case tar.TypeReg:
outFile, err := os.Create(path)
if err != nil {
return err
}
if _, err := io.Copy(outFile, tr); err != nil {
outFile.Close()
return err
}
outFile.Close()
}
}
return nil
}
// @Title createJavaCliWrapper
// @Description Create the Java CLI wrapper
// @Param binPath string true "The binary path"
// @Success 200 {string} string "The created file path"
// @router /createJavaCliWrapper [post]
func createJavaCliWrapper(binPath string) error {
if runtime.GOOS == "windows" {
// Create a Windows CMD file
cmdPath := filepath.Join(binPath, "casbin-java-cli.cmd")
cmdContent := fmt.Sprintf(`@echo off
java -jar "%s\casbin-java-cli.jar" %%*`, binPath)
err := os.WriteFile(cmdPath, []byte(cmdContent), 0o755)
if err != nil {
return fmt.Errorf("failed to create Java CLI wrapper: %v", err)
}
} else {
// Create Unix shell script
shPath := filepath.Join(binPath, "casbin-java-cli")
shContent := fmt.Sprintf(`#!/bin/sh
java -jar "%s/casbin-java-cli.jar" "$@"`, binPath)
err := os.WriteFile(shPath, []byte(shContent), 0o755)
if err != nil {
return fmt.Errorf("failed to create Java CLI wrapper: %v", err)
}
}
return nil
}
// @Title downloadCLI
// @Description Download and setup CLI tools
// @Success 200 {error} error "Error if any"
func downloadCLI() error {
pathEnv := os.Getenv("PATH")
binPath, err := filepath.Abs(downloadFolder)
if err != nil {
return fmt.Errorf("failed to get absolute path to download directory: %v", err)
}
if !strings.Contains(pathEnv, binPath) {
newPath := fmt.Sprintf("%s%s%s", binPath, string(os.PathListSeparator), pathEnv)
if err := os.Setenv("PATH", newPath); err != nil {
return fmt.Errorf("failed to update PATH environment variable: %v", err)
}
}
if err := os.MkdirAll(downloadFolder, 0o755); err != nil {
return fmt.Errorf("failed to create download directory: %v", err)
}
repos := map[string]string{
"java": javaCliRepo,
"go": goCliRepo,
"rust": rustCliRepo,
}
for lang, repo := range repos {
cliURL, version, err := getLatestCLIURL(repo, lang)
if err != nil {
fmt.Printf("failed to get %s CLI URL: %v\n", lang, err)
continue
}
originalPath := filepath.Join(downloadFolder, getBinaryNames()[lang])
fmt.Printf("downloading %s CLI: %s\n", lang, cliURL)
client := proxy.GetHttpClient(cliURL)
resp, err := client.Get(cliURL)
if err != nil {
fmt.Printf("failed to download %s CLI: %v\n", lang, err)
continue
}
func() {
defer resp.Body.Close()
if err := os.MkdirAll(filepath.Dir(originalPath), 0o755); err != nil {
fmt.Printf("failed to create directory for %s CLI: %v\n", lang, err)
return
}
tmpFile := originalPath + ".tmp"
out, err := os.Create(tmpFile)
if err != nil {
fmt.Printf("failed to create or write %s CLI: %v\n", lang, err)
return
}
defer func() {
out.Close()
os.Remove(tmpFile)
}()
if _, err = io.Copy(out, resp.Body); err != nil ||
out.Close() != nil ||
os.Rename(tmpFile, originalPath) != nil {
fmt.Printf("failed to download %s CLI: %v\n", lang, err)
return
}
}()
if lang == "go" {
if err := extractGoCliFile(originalPath); err != nil {
fmt.Printf("failed to extract Go CLI: %v\n", err)
continue
}
} else {
finalPath := filepath.Join(downloadFolder, getFinalBinaryName(lang))
if err := os.Rename(originalPath, finalPath); err != nil {
fmt.Printf("failed to rename %s CLI: %v\n", lang, err)
continue
}
}
if runtime.GOOS != "windows" {
execPath := filepath.Join(downloadFolder, getFinalBinaryName(lang))
if err := os.Chmod(execPath, 0o755); err != nil {
fmt.Printf("failed to set %s CLI execution permission: %v\n", lang, err)
continue
}
}
fmt.Printf("downloaded %s CLI version: %s\n", lang, version)
if lang == "java" {
if err := createJavaCliWrapper(binPath); err != nil {
fmt.Printf("failed to create Java CLI wrapper: %v\n", err)
continue
}
}
}
return nil
}
// @Title RefreshEngines
// @Tag CLI API
// @Description Refresh all CLI engines
// @Param m query string true "Hash for request validation"
// @Param t query string true "Timestamp for request validation"
// @Success 200 {object} controllers.Response The Response object
// @router /refresh-engines [post]
func (c *ApiController) RefreshEngines() {
if !beego.AppConfig.DefaultBool("isDemoMode", false) {
c.ResponseError("refresh engines is only available in demo mode")
return
}
hash := c.Input().Get("m")
timestamp := c.Input().Get("t")
if hash == "" || timestamp == "" {
c.ResponseError("invalid identifier")
return
}
requestTime, err := time.Parse(time.RFC3339, timestamp)
if err != nil {
c.ResponseError("invalid identifier")
return
}
timeDiff := time.Since(requestTime)
if timeDiff > 5*time.Minute || timeDiff < -5*time.Minute {
c.ResponseError("invalid identifier")
return
}
version := "casbin-editor-v1"
rawString := fmt.Sprintf("%s|%s", version, timestamp)
hasher := sha256.New()
hasher.Write([]byte(rawString))
calculatedHash := strings.ToLower(hex.EncodeToString(hasher.Sum(nil)))
if calculatedHash != strings.ToLower(hash) {
c.ResponseError("invalid identifier")
return
}
err = downloadCLI()
if err != nil {
c.ResponseError(fmt.Sprintf("failed to refresh engines: %v", err))
return
}
c.ResponseOk(map[string]string{
"status": "success",
"message": "CLI engines updated successfully",
})
}
// @Title ScheduleCLIUpdater
// @Description Start periodic CLI update scheduler
func ScheduleCLIUpdater() {
if !beego.AppConfig.DefaultBool("isDemoMode", false) {
return
}
ticker := time.NewTicker(1 * time.Hour)
defer ticker.Stop()
for range ticker.C {
err := downloadCLI()
if err != nil {
fmt.Printf("failed to update CLI: %v\n", err)
} else {
fmt.Println("CLI updated successfully")
}
}
}
// @Title DownloadCLI
// @Description Download the CLI
// @Success 200 {string} string "The downloaded file path"
// @router /downloadCLI [post]
func DownloadCLI() error {
return downloadCLI()
}
// @Title InitCLIDownloader
// @Description Initialize CLI downloader and start update scheduler
func InitCLIDownloader() {
if !beego.AppConfig.DefaultBool("isDemoMode", false) {
return
}
util.SafeGoroutine(func() {
err := DownloadCLI()
if err != nil {
fmt.Printf("failed to initialize CLI downloader: %v\n", err)
}
ScheduleCLIUpdater()
})
}

View File

@ -1,4 +1,4 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
// 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.
@ -16,63 +16,231 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
xormadapter "github.com/casdoor/xorm-adapter/v3"
)
func (c *ApiController) Enforce() {
var permissionRule object.PermissionRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRule)
// GetEnforcers
// @Title GetEnforcers
// @Tag Enforcer API
// @Description get enforcers
// @Param owner query string true "The owner of enforcers"
// @Success 200 {array} object.Enforcer
// @router /get-enforcers [get]
func (c *ApiController) GetEnforcers() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
enforcers, err := object.GetEnforcers(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(enforcers)
} else {
limit := util.ParseInt(limit)
count, err := object.GetEnforcerCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
enforcers, err := object.GetPaginationEnforcers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(enforcers, paginator.Nums())
}
}
// GetEnforcer
// @Title GetEnforcer
// @Tag Enforcer API
// @Description get enforcer
// @Param id query string true "The id ( owner/name ) of enforcer"
// @Success 200 {object} object.Enforcer
// @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 {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.Enforce(&permissionRule)
c.ServeJSON()
if loadModelCfg == "true" && enforcer.Model != "" {
err := enforcer.LoadModelCfg()
if err != nil {
return
}
}
c.ResponseOk(enforcer)
}
func (c *ApiController) BatchEnforce() {
var permissionRules []object.PermissionRule
err := json.Unmarshal(c.Ctx.Input.RequestBody, &permissionRules)
// UpdateEnforcer
// @Title UpdateEnforcer
// @Tag Enforcer API
// @Description update enforcer
// @Param id query string true "The id ( owner/name ) of enforcer"
// @Param enforcer body object true "The enforcer object"
// @Success 200 {object} object.Enforcer
// @router /update-enforcer [post]
func (c *ApiController) UpdateEnforcer() {
id := c.Input().Get("id")
enforcer := object.Enforcer{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.BatchEnforce(permissionRules)
c.Data["json"] = wrapActionResponse(object.UpdateEnforcer(id, &enforcer))
c.ServeJSON()
}
func (c *ApiController) GetAllObjects() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
// AddEnforcer
// @Title AddEnforcer
// @Tag Enforcer API
// @Description add enforcer
// @Param enforcer body object true "The enforcer object"
// @Success 200 {object} object.Enforcer
// @router /add-enforcer [post]
func (c *ApiController) AddEnforcer() {
enforcer := object.Enforcer{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetAllObjects(userId)
c.Data["json"] = wrapActionResponse(object.AddEnforcer(&enforcer))
c.ServeJSON()
}
func (c *ApiController) GetAllActions() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
// DeleteEnforcer
// @Title DeleteEnforcer
// @Tag Enforcer API
// @Description delete enforcer
// @Param body body object.Enforcer true "The enforcer object"
// @Success 200 {object} object.Enforcer
// @router /delete-enforcer [post]
func (c *ApiController) DeleteEnforcer() {
var enforcer object.Enforcer
err := json.Unmarshal(c.Ctx.Input.RequestBody, &enforcer)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetAllActions(userId)
c.Data["json"] = wrapActionResponse(object.DeleteEnforcer(&enforcer))
c.ServeJSON()
}
func (c *ApiController) GetAllRoles() {
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
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
}
if adapter == nil {
c.ResponseError(fmt.Sprintf(c.T("enforcer:the adapter: %s is not found"), adapterId))
return
}
err = adapter.InitAdapter()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
return
}
c.Data["json"] = object.GetAllRoles(userId)
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()
}

55
controllers/face.go Normal file
View File

@ -0,0 +1,55 @@
// Copyright 2024 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.
// Casdoor will expose its providers as services to SDK
// We are going to implement those services as APIs here
package controllers
import (
"fmt"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// FaceIDSigninBegin
// @Title FaceIDSigninBegin
// @Tag Login API
// @Description FaceId Login Flow 1st stage
// @Param owner query string true "owner"
// @Param name query string true "name"
// @Success 200 {object} controllers.Response The Response object
// @router /faceid-signin-begin [get]
func (c *ApiController) FaceIDSigninBegin() {
userOwner := c.Input().Get("owner")
userName := c.Input().Get("name")
user, err := object.GetUserByFields(userOwner, userName)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(userOwner, userName)))
return
}
if len(user.FaceIds) == 0 {
c.ResponseError(c.T("check:Face data does not exist, cannot log in"))
return
}
c.ResponseOk()
}

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

187
controllers/group.go Normal file
View File

@ -0,0 +1,187 @@
// 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
package controllers
import (
"encoding/json"
"fmt"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetGroups
// @Title GetGroups
// @Tag Group API
// @Description get groups
// @Param owner query string true "The owner of groups"
// @Success 200 {array} object.Group The Response object
// @router /get-groups [get]
func (c *ApiController) GetGroups() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
withTree := c.Input().Get("withTree")
if limit == "" || page == "" {
groups, err := object.GetGroups(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
err = object.ExtendGroupsWithUsers(groups)
if err != nil {
c.ResponseError(err.Error())
return
}
if withTree == "true" {
c.ResponseOk(object.ConvertToTreeData(groups, owner))
return
}
c.ResponseOk(groups)
} else {
limit := util.ParseInt(limit)
count, err := object.GetGroupCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
groups, err := object.GetPaginationGroups(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
groupsHaveChildrenMap, err := object.GetGroupsHaveChildrenMap(groups)
if err != nil {
c.ResponseError(err.Error())
return
}
for _, group := range groups {
_, ok := groupsHaveChildrenMap[group.GetId()]
if ok {
group.HaveChildren = true
}
parent, ok := groupsHaveChildrenMap[fmt.Sprintf("%s/%s", group.Owner, group.ParentId)]
if ok {
group.ParentName = parent.DisplayName
}
}
err = object.ExtendGroupsWithUsers(groups)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(groups, paginator.Nums())
}
}
// GetGroup
// @Title GetGroup
// @Tag Group API
// @Description get group
// @Param id query string true "The id ( owner/name ) of the group"
// @Success 200 {object} object.Group The Response object
// @router /get-group [get]
func (c *ApiController) GetGroup() {
id := c.Input().Get("id")
group, err := object.GetGroup(id)
if err != nil {
c.ResponseError(err.Error())
return
}
err = object.ExtendGroupWithUsers(group)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(group)
}
// UpdateGroup
// @Title UpdateGroup
// @Tag Group API
// @Description update group
// @Param id query string true "The id ( owner/name ) of the group"
// @Param body body object.Group true "The details of the group"
// @Success 200 {object} controllers.Response The Response object
// @router /update-group [post]
func (c *ApiController) UpdateGroup() {
id := c.Input().Get("id")
var group object.Group
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateGroup(id, &group))
c.ServeJSON()
}
// AddGroup
// @Title AddGroup
// @Tag Group API
// @Description add group
// @Param body body object.Group true "The details of the group"
// @Success 200 {object} controllers.Response The Response object
// @router /add-group [post]
func (c *ApiController) AddGroup() {
var group object.Group
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddGroup(&group))
c.ServeJSON()
}
// DeleteGroup
// @Title DeleteGroup
// @Tag Group API
// @Description delete group
// @Param body body object.Group true "The details of the group"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-group [post]
func (c *ApiController) DeleteGroup() {
var group object.Group
err := json.Unmarshal(c.Ctx.Input.RequestBody, &group)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteGroup(&group))
c.ServeJSON()
}

View File

@ -0,0 +1,56 @@
// Copyright 2025 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 (
"fmt"
"os"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func (c *ApiController) UploadGroups() {
userId := c.GetSessionUsername()
owner, user := util.GetOwnerAndNameFromId(userId)
file, header, err := c.Ctx.Request.FormFile("file")
if err != nil {
c.ResponseError(err.Error())
return
}
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
path := util.GetUploadXlsxPath(fileId)
defer os.Remove(path)
err = saveFile(path, &file)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UploadGroups(owner, path)
if err != nil {
c.ResponseError(err.Error())
return
}
if affected {
c.ResponseOk()
} else {
c.ResponseError(c.T("general:Failed to import groups"))
}
}

190
controllers/invitation.go Normal file
View File

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

View File

@ -21,142 +21,156 @@ import (
"github.com/casdoor/casdoor/util"
)
type LdapServer struct {
Host string `json:"host"`
Port int `json:"port"`
Admin string `json:"admin"`
Passwd string `json:"passwd"`
BaseDn string `json:"baseDn"`
}
type LdapResp struct {
// Groups []LdapRespGroup `json:"groups"`
Users []object.LdapRespUser `json:"users"`
Users []object.LdapUser `json:"users"`
ExistUuids []string `json:"existUuids"`
}
//type LdapRespGroup struct {
// type LdapRespGroup struct {
// GroupId string
// GroupName string
//}
// }
type LdapSyncResp struct {
Exist []object.LdapRespUser `json:"exist"`
Failed []object.LdapRespUser `json:"failed"`
Exist []object.LdapUser `json:"exist"`
Failed []object.LdapUser `json:"failed"`
}
// GetLdapUser
// @Tag Account API
// GetLdapUsers
// @Title GetLdapser
// @router /get-ldap-user [post]
func (c *ApiController) GetLdapUser() {
ldapServer := LdapServer{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldapServer)
if err != nil || util.IsStrsEmpty(ldapServer.Host, ldapServer.Admin, ldapServer.Passwd, ldapServer.BaseDn) {
c.ResponseError(c.T("general:Missing parameter"))
return
}
// @Tag Account API
// @Description get ldap users
// Param id string true "id"
// @Success 200 {object} controllers.LdapResp The Response object
// @router /get-ldap-users [get]
func (c *ApiController) GetLdapUsers() {
id := c.Input().Get("id")
var resp LdapResp
conn, err := object.GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
_, ldapId := util.GetOwnerAndNameFromId(id)
ldapServer, err := object.GetLdap(ldapId)
if err != nil {
c.ResponseError(err.Error())
return
}
//groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
//if err != nil {
conn, err := ldapServer.GetLdapConn()
if err != nil {
c.ResponseError(err.Error())
return
}
defer conn.Close()
// groupsMap, err := conn.GetLdapGroups(ldapServer.BaseDn)
// if err != nil {
// c.ResponseError(err.Error())
// return
//}
// }
//for _, group := range groupsMap {
// for _, group := range groupsMap {
// resp.Groups = append(resp.Groups, LdapRespGroup{
// GroupId: group.GidNumber,
// GroupName: group.Cn,
// })
//}
// }
users, err := conn.GetLdapUsers(ldapServer.BaseDn)
users, err := conn.GetLdapUsers(ldapServer)
if err != nil {
c.ResponseError(err.Error())
return
}
for _, user := range users {
resp.Users = append(resp.Users, object.LdapRespUser{
UidNumber: user.UidNumber,
Uid: user.Uid,
Cn: user.Cn,
GroupId: user.GidNumber,
// GroupName: groupsMap[user.GidNumber].Cn,
Uuid: user.Uuid,
Email: util.GetMaxLenStr(user.Mail, user.Email, user.EmailAddress),
Phone: util.GetMaxLenStr(user.TelephoneNumber, user.Mobile, user.MobileTelephoneNumber),
Address: util.GetMaxLenStr(user.RegisteredAddress, user.PostalAddress),
})
uuids := make([]string, len(users))
for i, user := range users {
uuids[i] = user.GetLdapUuid()
}
existUuids, err := object.GetExistUuids(ldapServer.Owner, uuids)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = Response{Status: "ok", Data: resp}
c.ServeJSON()
resp := LdapResp{
Users: object.AutoAdjustLdapUser(users),
ExistUuids: existUuids,
}
c.ResponseOk(resp)
}
// GetLdaps
// @Tag Account API
// @Title GetLdaps
// @router /get-ldaps [post]
// @Tag Account API
// @Description get ldaps
// @Param owner query string false "owner"
// @Success 200 {array} object.Ldap The Response object
// @router /get-ldaps [get]
func (c *ApiController) GetLdaps() {
owner := c.Input().Get("owner")
c.Data["json"] = Response{Status: "ok", Data: object.GetLdaps(owner)}
c.ServeJSON()
c.ResponseOk(object.GetMaskedLdaps(object.GetLdaps(owner)))
}
// GetLdap
// @Tag Account API
// @Title GetLdap
// @router /get-ldap [post]
// @Tag Account API
// @Description get ldap
// @Param id query string true "id"
// @Success 200 {object} object.Ldap The Response object
// @router /get-ldap [get]
func (c *ApiController) GetLdap() {
id := c.Input().Get("id")
if util.IsStrsEmpty(id) {
if util.IsStringsEmpty(id) {
c.ResponseError(c.T("general:Missing parameter"))
return
}
c.Data["json"] = Response{Status: "ok", Data: object.GetLdap(id)}
c.ServeJSON()
_, name := util.GetOwnerAndNameFromId(id)
ldap, err := object.GetLdap(name)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedLdap(ldap))
}
// AddLdap
// @Tag Account API
// @Title AddLdap
// @Tag Account API
// @Description add ldap
// @Param body body object.Ldap true "The details of the ldap"
// @Success 200 {object} controllers.Response The Response object
// @router /add-ldap [post]
func (c *ApiController) AddLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
if err != nil {
c.ResponseError(err.Error())
return
}
if util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
c.ResponseError(c.T("general:Missing parameter"))
return
}
if util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
c.ResponseError(c.T("general:Missing parameter"))
if ok, err := object.CheckLdapExist(&ldap); err != nil {
c.ResponseError(err.Error())
return
}
if object.CheckLdapExist(&ldap) {
} else if ok {
c.ResponseError(c.T("ldap:Ldap server exist"))
return
}
affected := object.AddLdap(&ldap)
resp := wrapActionResponse(affected)
if affected {
resp.Data2 = ldap
}
resp := wrapActionResponse(object.AddLdap(&ldap))
resp.Data2 = ldap
if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
err = object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
if err != nil {
c.ResponseError(err.Error())
return
}
}
c.Data["json"] = resp
@ -164,36 +178,52 @@ func (c *ApiController) AddLdap() {
}
// UpdateLdap
// @Tag Account API
// @Title UpdateLdap
// @Tag Account API
// @Description update ldap
// @Param body body object.Ldap true "The details of the ldap"
// @Success 200 {object} controllers.Response The Response object
// @router /update-ldap [post]
func (c *ApiController) UpdateLdap() {
var ldap object.Ldap
err := json.Unmarshal(c.Ctx.Input.RequestBody, &ldap)
if err != nil || util.IsStrsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Admin, ldap.Passwd, ldap.BaseDn) {
if err != nil || util.IsStringsEmpty(ldap.Owner, ldap.ServerName, ldap.Host, ldap.Username, ldap.Password, ldap.BaseDn) {
c.ResponseError(c.T("general:Missing parameter"))
return
}
prevLdap := object.GetLdap(ldap.Id)
affected := object.UpdateLdap(&ldap)
resp := wrapActionResponse(affected)
if affected {
resp.Data2 = ldap
prevLdap, err := object.GetLdap(ldap.Id)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UpdateLdap(&ldap)
if err != nil {
c.ResponseError(err.Error())
return
}
if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
err := object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
if err != nil {
c.ResponseError(err.Error())
return
}
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
}
c.Data["json"] = resp
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
// DeleteLdap
// @Tag Account API
// @Title DeleteLdap
// @Tag Account API
// @Description delete ldap
// @Param body body object.Ldap true "The details of the ldap"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-ldap [post]
func (c *ApiController) DeleteLdap() {
var ldap object.Ldap
@ -203,49 +233,50 @@ func (c *ApiController) DeleteLdap() {
return
}
affected, err := object.DeleteLdap(&ldap)
if err != nil {
c.ResponseError(err.Error())
return
}
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
c.Data["json"] = wrapActionResponse(object.DeleteLdap(&ldap))
c.Data["json"] = wrapActionResponse(affected)
c.ServeJSON()
}
// SyncLdapUsers
// @Tag Account API
// @Title SyncLdapUsers
// @Tag Account API
// @Description sync ldap users
// @Param id query string true "id"
// @Success 200 {object} controllers.LdapSyncResp The Response object
// @router /sync-ldap-users [post]
func (c *ApiController) SyncLdapUsers() {
owner := c.Input().Get("owner")
ldapId := c.Input().Get("ldapId")
var users []object.LdapRespUser
id := c.Input().Get("id")
owner, ldapId := util.GetOwnerAndNameFromId(id)
var users []object.LdapUser
err := json.Unmarshal(c.Ctx.Input.RequestBody, &users)
if err != nil {
c.ResponseError(err.Error())
return
}
object.UpdateLdapSyncTime(ldapId)
exist, failed := object.SyncLdapUsers(owner, users, ldapId)
c.Data["json"] = &Response{Status: "ok", Data: &LdapSyncResp{
Exist: *exist,
Failed: *failed,
}}
c.ServeJSON()
}
// CheckLdapUsersExist
// @Tag Account API
// @Title CheckLdapUserExist
// @router /check-ldap-users-exist [post]
func (c *ApiController) CheckLdapUsersExist() {
owner := c.Input().Get("owner")
var uuids []string
err := json.Unmarshal(c.Ctx.Input.RequestBody, &uuids)
err = object.UpdateLdapSyncTime(ldapId)
if err != nil {
c.ResponseError(err.Error())
return
}
exist := object.CheckLdapUuidExist(owner, uuids)
c.Data["json"] = &Response{Status: "ok", Data: exist}
c.ServeJSON()
exist, failed, err := object.SyncLdapUsers(owner, users, ldapId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(&LdapSyncResp{
Exist: exist,
Failed: failed,
})
}

View File

@ -1,118 +0,0 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"fmt"
"log"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/forestmgy/ldapserver"
"github.com/lor00x/goldap/message"
)
func StartLdapServer() {
server := ldapserver.NewServer()
routes := ldapserver.NewRouteMux()
routes.Bind(handleBind)
routes.Search(handleSearch).Label(" SEARCH****")
server.Handle(routes)
server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
}
func handleBind(w ldapserver.ResponseWriter, m *ldapserver.Message) {
r := m.GetBindRequest()
res := ldapserver.NewBindResponse(ldapserver.LDAPResultSuccess)
if r.AuthenticationChoice() == "simple" {
bindusername, bindorg, err := object.GetNameAndOrgFromDN(string(r.Name()))
if err != "" {
log.Printf("Bind failed ,ErrMsg=%s", err)
res.SetResultCode(ldapserver.LDAPResultInvalidDNSyntax)
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
w.Write(res)
return
}
bindpassword := string(r.AuthenticationSimple())
binduser, err := object.CheckUserPassword(bindorg, bindusername, bindpassword, "en")
if err != "" {
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
res.SetResultCode(ldapserver.LDAPResultInvalidCredentials)
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
w.Write(res)
return
}
if bindorg == "built-in" {
m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true
} else if binduser.IsAdmin {
m.Client.IsOrgAdmin = true
}
m.Client.IsAuthenticated = true
m.Client.UserName = bindusername
m.Client.OrgName = bindorg
} else {
res.SetResultCode(ldapserver.LDAPResultAuthMethodNotSupported)
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
}
w.Write(res)
}
func handleSearch(w ldapserver.ResponseWriter, m *ldapserver.Message) {
res := ldapserver.NewSearchResultDoneResponse(ldapserver.LDAPResultSuccess)
if !m.Client.IsAuthenticated {
res.SetResultCode(ldapserver.LDAPResultUnwillingToPerform)
w.Write(res)
return
}
r := m.GetSearchRequest()
if r.FilterString() == "(objectClass=*)" {
w.Write(res)
return
}
name, org, errCode := object.GetUserNameAndOrgFromBaseDnAndFilter(string(r.BaseObject()), r.FilterString())
if errCode != ldapserver.LDAPResultSuccess {
res.SetResultCode(errCode)
w.Write(res)
return
}
// Handle Stop Signal (server stop / client disconnected / Abandoned request....)
select {
case <-m.Done:
log.Print("Leaving handleSearch...")
return
default:
}
users, errCode := object.GetFilteredUsers(m, name, org)
if errCode != ldapserver.LDAPResultSuccess {
res.SetResultCode(errCode)
w.Write(res)
return
}
for i := 0; i < len(users); i++ {
user := users[i]
dn := fmt.Sprintf("cn=%s,%s", user.DisplayName, string(r.BaseObject()))
e := ldapserver.NewSearchResultEntry(dn)
e.AddAttribute("cn", message.AttributeValue(user.Name))
e.AddAttribute("uid", message.AttributeValue(user.Name))
e.AddAttribute("email", message.AttributeValue(user.Email))
e.AddAttribute("mobile", message.AttributeValue(user.Phone))
// e.AddAttribute("postalAddress", message.AttributeValue(user.Address[0]))
w.Write(e)
}
w.Write(res)
}

View File

@ -26,8 +26,10 @@ type LinkForm struct {
}
// Unlink ...
// @router /unlink [post]
// @Tag Login API
// @Title Unlink
// @router /unlink [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) Unlink() {
user, ok := c.RequireSignedInUser()
if !ok {
@ -45,15 +47,19 @@ 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 := object.GetApplicationByUser(user)
application, err := object.GetApplicationByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(c.T("link:You can't unlink yourself, you are not a member of any application"))
return
@ -88,8 +94,17 @@ func (c *ApiController) Unlink() {
return
}
object.ClearUserOAuthProperties(&unlinkedUser, providerType)
_, err = object.ClearUserOAuthProperties(&unlinkedUser, providerType)
if err != nil {
c.ResponseError(err.Error())
return
}
_, err = object.LinkUserAccount(&unlinkedUser, providerType, "")
if err != nil {
c.ResponseError(err.Error())
return
}
object.LinkUserAccount(&unlinkedUser, providerType, "")
c.ResponseOk()
}

282
controllers/mfa.go Normal file
View File

@ -0,0 +1,282 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"net/http"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"github.com/google/uuid"
)
// MfaSetupInitiate
// @Title MfaSetupInitiate
// @Tag MFA API
// @Description setup MFA
// @param owner form string true "owner of user"
// @param name form string true "name of user"
// @param type form string true "MFA auth type"
// @Success 200 {object} controllers.Response The Response object
// @router /mfa/setup/initiate [post]
func (c *ApiController) MfaSetupInitiate() {
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
mfaType := c.Ctx.Request.Form.Get("mfaType")
userId := util.GetId(owner, name)
if len(userId) == 0 {
c.ResponseError(http.StatusText(http.StatusBadRequest))
return
}
MfaUtil := object.GetMfaUtil(mfaType, nil)
if MfaUtil == nil {
c.ResponseError("Invalid auth type")
}
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError("User doesn't exist")
return
}
mfaProps, err := MfaUtil.Initiate(user.GetId())
if err != nil {
c.ResponseError(err.Error())
return
}
recoveryCode := uuid.NewString()
mfaProps.RecoveryCodes = []string{recoveryCode}
resp := mfaProps
c.ResponseOk(resp)
}
// MfaSetupVerify
// @Title MfaSetupVerify
// @Tag MFA API
// @Description setup verify totp
// @param secret form string true "MFA secret"
// @param passcode form string true "MFA passcode"
// @Success 200 {object} controllers.Response The Response object
// @router /mfa/setup/verify [post]
func (c *ApiController) MfaSetupVerify() {
mfaType := c.Ctx.Request.Form.Get("mfaType")
passcode := c.Ctx.Request.Form.Get("passcode")
secret := c.Ctx.Request.Form.Get("secret")
dest := c.Ctx.Request.Form.Get("dest")
countryCode := c.Ctx.Request.Form.Get("countryCode")
if mfaType == "" || passcode == "" {
c.ResponseError("missing auth type or passcode")
return
}
config := &object.MfaProps{
MfaType: mfaType,
}
if mfaType == object.TotpType {
if secret == "" {
c.ResponseError("totp secret is missing")
return
}
config.Secret = secret
} else if mfaType == object.SmsType {
if dest == "" {
c.ResponseError("destination is missing")
return
}
config.Secret = dest
if countryCode == "" {
c.ResponseError("country code is missing")
return
}
config.CountryCode = countryCode
} else if mfaType == object.EmailType {
if dest == "" {
c.ResponseError("destination is missing")
return
}
config.Secret = dest
}
mfaUtil := object.GetMfaUtil(mfaType, config)
if mfaUtil == nil {
c.ResponseError("Invalid multi-factor authentication type")
return
}
err := mfaUtil.SetupVerify(passcode)
if err != nil {
c.ResponseError(err.Error())
} else {
c.ResponseOk(http.StatusText(http.StatusOK))
}
}
// MfaSetupEnable
// @Title MfaSetupEnable
// @Tag MFA API
// @Description enable totp
// @param owner form string true "owner of user"
// @param name form string true "name of user"
// @param type form string true "MFA auth type"
// @Success 200 {object} controllers.Response The Response object
// @router /mfa/setup/enable [post]
func (c *ApiController) MfaSetupEnable() {
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
mfaType := c.Ctx.Request.Form.Get("mfaType")
secret := c.Ctx.Request.Form.Get("secret")
dest := c.Ctx.Request.Form.Get("dest")
countryCode := c.Ctx.Request.Form.Get("secret")
recoveryCodes := c.Ctx.Request.Form.Get("recoveryCodes")
user, err := object.GetUser(util.GetId(owner, name))
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError("User doesn't exist")
return
}
config := &object.MfaProps{
MfaType: mfaType,
}
if mfaType == object.TotpType {
if secret == "" {
c.ResponseError("totp secret is missing")
return
}
config.Secret = secret
} else if mfaType == object.EmailType {
if user.Email == "" {
if dest == "" {
c.ResponseError("destination is missing")
return
}
user.Email = dest
}
} else if mfaType == object.SmsType {
if user.Phone == "" {
if dest == "" {
c.ResponseError("destination is missing")
return
}
user.Phone = dest
if countryCode == "" {
c.ResponseError("country code is missing")
return
}
user.CountryCode = countryCode
}
}
if recoveryCodes == "" {
c.ResponseError("recovery codes is missing")
return
}
config.RecoveryCodes = []string{recoveryCodes}
mfaUtil := object.GetMfaUtil(mfaType, config)
if mfaUtil == nil {
c.ResponseError("Invalid multi-factor authentication type")
return
}
err = mfaUtil.Enable(user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(http.StatusText(http.StatusOK))
}
// DeleteMfa
// @Title DeleteMfa
// @Tag MFA API
// @Description: Delete MFA
// @param owner form string true "owner of user"
// @param name form string true "name of user"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-mfa/ [post]
func (c *ApiController) DeleteMfa() {
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
userId := util.GetId(owner, name)
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError("User doesn't exist")
return
}
err = object.DisabledMultiFactorAuth(user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetAllMfaProps(user, true))
}
// SetPreferredMfa
// @Title SetPreferredMfa
// @Tag MFA API
// @Description: Set specific Mfa Preferred
// @param owner form string true "owner of user"
// @param name form string true "name of user"
// @param id form string true "id of user's MFA props"
// @Success 200 {object} controllers.Response The Response object
// @router /set-preferred-mfa [post]
func (c *ApiController) SetPreferredMfa() {
mfaType := c.Ctx.Request.Form.Get("mfaType")
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
userId := util.GetId(owner, name)
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError("User doesn't exist")
return
}
err = object.SetPreferredMultiFactorAuth(user, mfaType)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetAllMfaProps(user, true))
}

View File

@ -37,13 +37,30 @@ func (c *ApiController) GetModels() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetModels(owner)
c.ServeJSON()
models, err := object.GetModels(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(models)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetModelCount(owner, field, value)))
models := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetModelCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
models, err := object.GetPaginationModels(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(models, paginator.Nums())
}
}
@ -52,21 +69,26 @@ func (c *ApiController) GetModels() {
// @Title GetModel
// @Tag Model API
// @Description get model
// @Param id query string true "The id of the model"
// @Param id query string true "The id ( owner/name ) of the model"
// @Success 200 {object} object.Model The Response object
// @router /get-model [get]
func (c *ApiController) GetModel() {
id := c.Input().Get("id")
c.Data["json"] = object.GetModel(id)
c.ServeJSON()
model, err := object.GetModel(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(model)
}
// UpdateModel
// @Title UpdateModel
// @Tag Model API
// @Description update model
// @Param id query string true "The id of the model"
// @Param id query string true "The id ( owner/name ) of the model"
// @Param body body object.Model true "The details of the model"
// @Success 200 {object} controllers.Response The Response object
// @router /update-model [post]
@ -80,7 +102,7 @@ func (c *ApiController) UpdateModel() {
return
}
c.Data["json"] = wrapActionResponse(object.UpdateModel(id, &model))
c.Data["json"] = wrapErrorResponse(object.UpdateModelWithCheck(id, &model))
c.ServeJSON()
}

View File

@ -14,7 +14,11 @@
package controllers
import "github.com/casdoor/casdoor/object"
import (
"strings"
"github.com/casdoor/casdoor/object"
)
// GetOidcDiscovery
// @Title GetOidcDiscovery
@ -42,3 +46,31 @@ func (c *RootController) GetJwks() {
c.Data["json"] = jwks
c.ServeJSON()
}
// GetWebFinger
// @Title GetWebFinger
// @Tag OIDC API
// @Param resource query string true "resource"
// @Success 200 {object} object.WebFinger
// @router /.well-known/webfinger [get]
func (c *RootController) GetWebFinger() {
resource := c.Input().Get("resource")
rels := []string{}
host := c.Ctx.Request.Host
for key, value := range c.Input() {
if strings.HasPrefix(key, "rel") {
rels = append(rels, value...)
}
}
webfinger, err := object.GetWebFinger(resource, rels, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = webfinger
c.Ctx.Output.ContentType("application/jrd+json")
c.ServeJSON()
}

View File

@ -37,14 +37,49 @@ func (c *ApiController) GetOrganizations() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organizationName := c.Input().Get("organizationName")
isGlobalAdmin := c.IsGlobalAdmin()
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedOrganizations(object.GetOrganizations(owner))
c.ServeJSON()
var organizations []*object.Organization
var err error
if isGlobalAdmin {
organizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner))
} else {
organizations, err = object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
}
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(organizations)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetOrganizationCount(owner, field, value)))
organizations := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
c.ResponseOk(organizations, paginator.Nums())
if !isGlobalAdmin {
organizations, err := object.GetMaskedOrganizations(object.GetOrganizations(owner, c.getCurrentUser().Owner))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(organizations)
} else {
limit := util.ParseInt(limit)
count, err := object.GetOrganizationCount(owner, organizationName, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
organizations, err := object.GetMaskedOrganizations(object.GetPaginationOrganizations(owner, organizationName, paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(organizations, paginator.Nums())
}
}
}
@ -57,16 +92,20 @@ func (c *ApiController) GetOrganizations() {
// @router /get-organization [get]
func (c *ApiController) GetOrganization() {
id := c.Input().Get("id")
organization, err := object.GetMaskedOrganization(object.GetOrganization(id))
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetMaskedOrganization(object.GetOrganization(id))
c.ServeJSON()
c.ResponseOk(organization)
}
// UpdateOrganization ...
// @Title UpdateOrganization
// @Tag Organization API
// @Description update organization
// @Param id query string true "The id of the organization"
// @Param id query string true "The id ( owner/name ) of the organization"
// @Param body body object.Organization true "The details of the organization"
// @Success 200 {object} controllers.Response The Response object
// @router /update-organization [post]
@ -80,7 +119,14 @@ func (c *ApiController) UpdateOrganization() {
return
}
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization))
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
c.ResponseError(err.Error())
return
}
isGlobalAdmin, _ := c.isGlobalAdmin()
c.Data["json"] = wrapActionResponse(object.UpdateOrganization(id, &organization, isGlobalAdmin))
c.ServeJSON()
}
@ -99,8 +145,18 @@ func (c *ApiController) AddOrganization() {
return
}
count := object.GetOrganizationCount("", "", "")
if err := checkQuotaForOrganization(count); err != nil {
count, err := object.GetOrganizationCount("", "", "", "")
if err != nil {
c.ResponseError(err.Error())
return
}
if err = checkQuotaForOrganization(int(count)); err != nil {
c.ResponseError(err.Error())
return
}
if err = object.CheckIpWhitelist(organization.IpWhitelist, c.GetAcceptLanguage()); err != nil {
c.ResponseError(err.Error())
return
}
@ -133,7 +189,7 @@ func (c *ApiController) DeleteOrganization() {
// @Tag Organization API
// @Description get default application
// @Param id query string true "organization id"
// @Success 200 {object} Response The Response object
// @Success 200 {object} controllers.Response The Response object
// @router /get-default-application [get]
func (c *ApiController) GetDefaultApplication() {
userId := c.GetSessionUsername()
@ -145,6 +201,24 @@ func (c *ApiController) GetDefaultApplication() {
return
}
maskedApplication := object.GetMaskedApplication(application, userId)
c.ResponseOk(maskedApplication)
application = object.GetMaskedApplication(application, userId)
c.ResponseOk(application)
}
// GetOrganizationNames ...
// @Title GetOrganizationNames
// @Tag Organization API
// @Param owner query string true "owner"
// @Description get all organization name and displayName
// @Success 200 {array} object.Organization The Response object
// @router /get-organization-names [get]
func (c *ApiController) GetOrganizationNames() {
owner := c.Input().Get("owner")
organizationNames, err := object.GetOrganizationsByFields(owner, []string{"name", "display_name"}...)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(organizationNames)
}

View File

@ -16,7 +16,6 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -38,13 +37,30 @@ func (c *ApiController) GetPayments() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetPayments(owner)
c.ServeJSON()
payments, err := object.GetPayments(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payments)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetPaymentCount(owner, field, value)))
payments := object.GetPaginationPayments(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetPaymentCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
payments, err := object.GetPaginationPayments(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payments, paginator.Nums())
}
}
@ -60,10 +76,14 @@ func (c *ApiController) GetPayments() {
// @router /get-user-payments [get]
func (c *ApiController) GetUserPayments() {
owner := c.Input().Get("owner")
organization := c.Input().Get("organization")
user := c.Input().Get("user")
payments := object.GetUserPayments(owner, organization, user)
payments, err := object.GetUserPayments(owner, user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payments)
}
@ -71,21 +91,26 @@ func (c *ApiController) GetUserPayments() {
// @Title GetPayment
// @Tag Payment API
// @Description get payment
// @Param id query string true "The id of the payment"
// @Param id query string true "The id ( owner/name ) of the payment"
// @Success 200 {object} object.Payment The Response object
// @router /get-payment [get]
func (c *ApiController) GetPayment() {
id := c.Input().Get("id")
c.Data["json"] = object.GetPayment(id)
c.ServeJSON()
payment, err := object.GetPayment(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payment)
}
// UpdatePayment
// @Title UpdatePayment
// @Tag Payment API
// @Description update payment
// @Param id query string true "The id of the payment"
// @Param id query string true "The id ( owner/name ) of the payment"
// @Param body body object.Payment true "The details of the payment"
// @Success 200 {object} controllers.Response The Response object
// @router /update-payment [post]
@ -150,35 +175,35 @@ func (c *ApiController) DeletePayment() {
// @router /notify-payment [post]
func (c *ApiController) NotifyPayment() {
owner := c.Ctx.Input.Param(":owner")
providerName := c.Ctx.Input.Param(":provider")
productName := c.Ctx.Input.Param(":product")
paymentName := c.Ctx.Input.Param(":payment")
body := c.Ctx.Input.RequestBody
ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
if ok {
_, err := c.Ctx.ResponseWriter.Write([]byte("success"))
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
panic(fmt.Errorf("NotifyPayment() failed: %v", ok))
payment, err := object.NotifyPayment(body, owner, paymentName)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payment)
}
// InvoicePayment
// @Title InvoicePayment
// @Tag Payment API
// @Description invoice payment
// @Param id query string true "The id of the payment"
// @Param id query string true "The id ( owner/name ) of the payment"
// @Success 200 {object} controllers.Response The Response object
// @router /invoice-payment [post]
func (c *ApiController) InvoicePayment() {
id := c.Input().Get("id")
payment := object.GetPayment(id)
payment, err := object.GetPayment(id)
if err != nil {
c.ResponseError(err.Error())
return
}
invoiceUrl, err := object.InvoicePayment(payment)
if err != nil {
c.ResponseError(err.Error())

View File

@ -37,13 +37,30 @@ func (c *ApiController) GetPermissions() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetPermissions(owner)
c.ServeJSON()
permissions, err := object.GetPermissions(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(permissions)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetPermissionCount(owner, field, value)))
permissions := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetPermissionCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
permissions, err := object.GetPaginationPermissions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(permissions, paginator.Nums())
}
}
@ -60,44 +77,57 @@ func (c *ApiController) GetPermissionsBySubmitter() {
return
}
permissions := object.GetPermissionsBySubmitter(user.Owner, user.Name)
permissions, err := object.GetPermissionsBySubmitter(user.Owner, user.Name)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(permissions, len(permissions))
return
}
// GetPermissionsByRole
// @Title GetPermissionsByRole
// @Tag Permission API
// @Description get permissions by role
// @Param id query string true "The id of the role"
// @Param id query string true "The id ( owner/name ) of the role"
// @Success 200 {array} object.Permission The Response object
// @router /get-permissions-by-role [get]
func (c *ApiController) GetPermissionsByRole() {
id := c.Input().Get("id")
permissions := object.GetPermissionsByRole(id)
permissions, err := object.GetPermissionsByRole(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(permissions, len(permissions))
return
}
// GetPermission
// @Title GetPermission
// @Tag Permission API
// @Description get permission
// @Param id query string true "The id of the permission"
// @Param id query string true "The id ( owner/name ) of the permission"
// @Success 200 {object} object.Permission The Response object
// @router /get-permission [get]
func (c *ApiController) GetPermission() {
id := c.Input().Get("id")
c.Data["json"] = object.GetPermission(id)
c.ServeJSON()
permission, err := object.GetPermission(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(permission)
}
// UpdatePermission
// @Title UpdatePermission
// @Tag Permission API
// @Description update permission
// @Param id query string true "The id of the permission"
// @Param id query string true "The id ( owner/name ) of the permission"
// @Param body body object.Permission true "The details of the permission"
// @Success 200 {object} controllers.Response The Response object
// @router /update-permission [post]

View File

@ -0,0 +1,54 @@
// 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 (
"fmt"
"os"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func (c *ApiController) UploadPermissions() {
userId := c.GetSessionUsername()
owner, user := util.GetOwnerAndNameFromId(userId)
file, header, err := c.Ctx.Request.FormFile("file")
if err != nil {
c.ResponseError(err.Error())
return
}
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
path := util.GetUploadXlsxPath(fileId)
defer os.Remove(path)
err = saveFile(path, &file)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UploadPermissions(owner, path)
if err != nil {
c.ResponseError(err.Error())
}
if affected {
c.ResponseOk()
} else {
c.ResponseError(c.T("general:Failed to import users"))
}
}

187
controllers/plan.go Normal file
View File

@ -0,0 +1,187 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetPlans
// @Title GetPlans
// @Tag Plan API
// @Description get plans
// @Param owner query string true "The owner of plans"
// @Success 200 {array} object.Plan The Response object
// @router /get-plans [get]
func (c *ApiController) GetPlans() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
plans, err := object.GetPlans(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(plans)
} else {
limit := util.ParseInt(limit)
count, err := object.GetPlanCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
plan, err := object.GetPaginatedPlans(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(plan, paginator.Nums())
}
}
// GetPlan
// @Title GetPlan
// @Tag Plan API
// @Description get plan
// @Param id query string true "The id ( owner/name ) of the plan"
// @Param includeOption query bool false "Should include plan's option"
// @Success 200 {object} object.Plan The Response object
// @router /get-plan [get]
func (c *ApiController) GetPlan() {
id := c.Input().Get("id")
includeOption := c.Input().Get("includeOption") == "true"
plan, err := object.GetPlan(id)
if err != nil {
c.ResponseError(err.Error())
return
}
if plan != nil && includeOption {
options, err := object.GetPermissionsByRole(plan.Role)
if err != nil {
c.ResponseError(err.Error())
return
}
for _, option := range options {
plan.Options = append(plan.Options, option.DisplayName)
}
}
c.ResponseOk(plan)
}
// UpdatePlan
// @Title UpdatePlan
// @Tag Plan API
// @Description update plan
// @Param id query string true "The id ( owner/name ) of the plan"
// @Param body body object.Plan true "The details of the plan"
// @Success 200 {object} controllers.Response The Response object
// @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()
}
// AddPlan
// @Title AddPlan
// @Tag Plan API
// @Description add plan
// @Param body body object.Plan true "The details of the plan"
// @Success 200 {object} controllers.Response The Response object
// @router /add-plan [post]
func (c *ApiController) AddPlan() {
var plan object.Plan
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
if err != nil {
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()
}
// DeletePlan
// @Title DeletePlan
// @Tag Plan API
// @Description delete plan
// @Param body body object.Plan true "The details of the plan"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-plan [post]
func (c *ApiController) DeletePlan() {
var plan object.Plan
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
if err != nil {
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()
}

145
controllers/pricing.go Normal file
View File

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

View File

@ -17,6 +17,7 @@ package controllers
import (
"encoding/json"
"fmt"
"strconv"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -38,13 +39,30 @@ func (c *ApiController) GetProducts() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetProducts(owner)
c.ServeJSON()
products, err := object.GetProducts(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(products)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProductCount(owner, field, value)))
products := object.GetPaginationProducts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetProductCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
products, err := object.GetPaginationProducts(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(products, paginator.Nums())
}
}
@ -53,24 +71,32 @@ func (c *ApiController) GetProducts() {
// @Title GetProduct
// @Tag Product API
// @Description get product
// @Param id query string true "The id of the product"
// @Param id query string true "The id ( owner/name ) of the product"
// @Success 200 {object} object.Product The Response object
// @router /get-product [get]
func (c *ApiController) GetProduct() {
id := c.Input().Get("id")
product := object.GetProduct(id)
object.ExtendProductWithProviders(product)
product, err := object.GetProduct(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = product
c.ServeJSON()
err = object.ExtendProductWithProviders(product)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(product)
}
// UpdateProduct
// @Title UpdateProduct
// @Tag Product API
// @Description update product
// @Param id query string true "The id of the product"
// @Param id query string true "The id ( owner/name ) of the product"
// @Param body body object.Product true "The details of the product"
// @Success 200 {object} controllers.Response The Response object
// @router /update-product [post]
@ -130,32 +156,59 @@ func (c *ApiController) DeleteProduct() {
// @Title BuyProduct
// @Tag Product API
// @Description buy product
// @Param id query string true "The id of the product"
// @Param id query string true "The id ( owner/name ) of the product"
// @Param providerName query string true "The name of the provider"
// @Success 200 {object} controllers.Response The Response object
// @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()
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
providerName := c.Input().Get("providerName")
paymentEnv := c.Input().Get("paymentEnv")
customPriceStr := c.Input().Get("customPrice")
if customPriceStr == "" {
customPriceStr = "0"
}
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return
}
payUrl, err := object.BuyProduct(id, providerName, user, host)
customPrice, err := strconv.ParseFloat(customPriceStr, 64)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payUrl)
// 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 != "" && paidUserName != c.GetSessionUsername() && !c.IsAdmin() {
c.ResponseError(c.T("general:Only admin user can specify user"))
return
}
if paidUserName == "" {
userId = c.GetSessionUsername()
}
if userId == "" {
c.ResponseError(c.T("general:Please login first"))
return
}
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return
}
payment, attachInfo, err := object.BuyProduct(id, user, providerName, pricingName, planName, host, paymentEnv, customPrice)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payment, attachInfo)
}

39
controllers/prometheus.go Normal file
View File

@ -0,0 +1,39 @@
// 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"
)
// GetPrometheusInfo
// @Title GetPrometheusInfo
// @Tag System API
// @Description get Prometheus Info
// @Success 200 {object} object.PrometheusInfo The Response object
// @router /get-prometheus-info [get]
func (c *ApiController) GetPrometheusInfo() {
_, ok := c.RequireAdmin()
if !ok {
return
}
prometheusInfo, err := object.GetPrometheusInfo()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(prometheusInfo)
}

View File

@ -37,13 +37,36 @@ func (c *ApiController) GetProviders() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
ok, isMaskEnabled := c.IsMaskedEnabled()
if !ok {
return
}
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedProviders(object.GetProviders(owner))
c.ServeJSON()
providers, err := object.GetProviders(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedProviders(providers, isMaskEnabled))
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetProviderCount(owner, field, value)))
providers := object.GetMaskedProviders(object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder))
count, err := object.GetProviderCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
paginationProviders, err := object.GetPaginationProviders(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
providers := object.GetMaskedProviders(paginationProviders, isMaskEnabled)
c.ResponseOk(providers, paginator.Nums())
}
}
@ -61,13 +84,36 @@ func (c *ApiController) GetGlobalProviders() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
ok, isMaskEnabled := c.IsMaskedEnabled()
if !ok {
return
}
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedProviders(object.GetGlobalProviders())
c.ServeJSON()
globalProviders, err := object.GetGlobalProviders()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedProviders(globalProviders, isMaskEnabled))
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalProviderCount(field, value)))
providers := object.GetMaskedProviders(object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder))
count, err := object.GetGlobalProviderCount(field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
paginationGlobalProviders, err := object.GetPaginationGlobalProviders(paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
providers := object.GetMaskedProviders(paginationGlobalProviders, isMaskEnabled)
c.ResponseOk(providers, paginator.Nums())
}
}
@ -76,20 +122,44 @@ func (c *ApiController) GetGlobalProviders() {
// @Title GetProvider
// @Tag Provider API
// @Description get provider
// @Param id query string true "The id of the provider"
// @Param id query string true "The id ( owner/name ) of the provider"
// @Success 200 {object} object.Provider The Response object
// @router /get-provider [get]
func (c *ApiController) GetProvider() {
id := c.Input().Get("id")
c.Data["json"] = object.GetMaskedProvider(object.GetProvider(id))
c.ServeJSON()
ok, isMaskEnabled := c.IsMaskedEnabled()
if !ok {
return
}
provider, err := object.GetProvider(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(object.GetMaskedProvider(provider, isMaskEnabled))
}
func (c *ApiController) requireProviderPermission(provider *object.Provider) bool {
isGlobalAdmin, user := c.isGlobalAdmin()
if isGlobalAdmin {
return true
}
if provider.Owner == "admin" || user.Owner != provider.Owner {
c.ResponseError(c.T("auth:Unauthorized operation"))
return false
}
return true
}
// UpdateProvider
// @Title UpdateProvider
// @Tag Provider API
// @Description update provider
// @Param id query string true "The id of the provider"
// @Param id query string true "The id ( owner/name ) of the provider"
// @Param body body object.Provider true "The details of the provider"
// @Success 200 {object} controllers.Response The Response object
// @router /update-provider [post]
@ -103,6 +173,11 @@ func (c *ApiController) UpdateProvider() {
return
}
ok := c.requireProviderPermission(&provider)
if !ok {
return
}
c.Data["json"] = wrapActionResponse(object.UpdateProvider(id, &provider))
c.ServeJSON()
}
@ -122,12 +197,23 @@ func (c *ApiController) AddProvider() {
return
}
count := object.GetProviderCount("", "", "")
if err := checkQuotaForProvider(count); err != nil {
count, err := object.GetProviderCount("", "", "")
if err != nil {
c.ResponseError(err.Error())
return
}
err = checkQuotaForProvider(int(count))
if err != nil {
c.ResponseError(err.Error())
return
}
ok := c.requireProviderPermission(&provider)
if !ok {
return
}
c.Data["json"] = wrapActionResponse(object.AddProvider(&provider))
c.ServeJSON()
}
@ -147,6 +233,11 @@ func (c *ApiController) DeleteProvider() {
return
}
ok := c.requireProviderPermission(&provider)
if !ok {
return
}
c.Data["json"] = wrapActionResponse(object.DeleteProvider(&provider))
c.ServeJSON()
}

View File

@ -17,6 +17,8 @@ package controllers
import (
"encoding/json"
"github.com/casvisor/casvisor-go-sdk/casvisorsdk"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
@ -42,14 +44,35 @@ func (c *ApiController) GetRecords() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organizationName := c.Input().Get("organizationName")
if limit == "" || page == "" {
c.Data["json"] = object.GetRecords()
c.ServeJSON()
records, err := object.GetRecords()
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(records)
} else {
limit := util.ParseInt(limit)
filterRecord := &object.Record{Organization: organization}
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRecordCount(field, value, filterRecord)))
records := object.GetPaginationRecords(paginator.Offset(), limit, field, value, sortField, sortOrder, filterRecord)
if c.IsGlobalAdmin() && organizationName != "" {
organization = organizationName
}
filterRecord := &casvisorsdk.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())
}
}
@ -62,17 +85,27 @@ func (c *ApiController) GetRecords() {
// @Success 200 {object} object.Record The Response object
// @router /get-records-filter [post]
func (c *ApiController) GetRecordsByFilter() {
_, ok := c.RequireAdmin()
if !ok {
return
}
body := string(c.Ctx.Input.RequestBody)
record := &object.Record{}
record := &casvisorsdk.Record{}
err := util.JsonToStruct(body, record)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetRecordsByField(record)
c.ServeJSON()
records, err := object.GetRecordsByField(record)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(records)
}
// AddRecord
@ -83,7 +116,7 @@ func (c *ApiController) GetRecordsByFilter() {
// @Success 200 {object} controllers.Response The Response object
// @router /add-record [post]
func (c *ApiController) AddRecord() {
var record object.Record
var record casvisorsdk.Record
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
if err != nil {
c.ResponseError(err.Error())

View File

@ -21,6 +21,7 @@ import (
"io"
"mime"
"path/filepath"
"strings"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -28,9 +29,19 @@ import (
)
// GetResources
// @router /get-resources [get]
// @Tag Resource API
// @Title GetResources
// @Description get resources
// @Param owner query string true "Owner"
// @Param user query string true "User"
// @Param pageSize query integer false "Page Size"
// @Param p query integer false "Page Number"
// @Param field query string false "Field"
// @Param value query string false "Value"
// @Param sortField query string false "Sort Field"
// @Param sortOrder query string false "Sort Order"
// @Success 200 {array} object.Resource The Response object
// @router /get-resources [get]
func (c *ApiController) GetResources() {
owner := c.Input().Get("owner")
user := c.Input().Get("user")
@ -41,21 +52,53 @@ func (c *ApiController) GetResources() {
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
userObj, ok := c.RequireSignedInUser()
isOrgAdmin, ok := c.IsOrgAdmin()
if !ok {
return
}
if userObj.IsAdmin {
if isOrgAdmin {
user = ""
}
if limit == "" || page == "" {
c.Data["json"] = object.GetResources(owner, user)
c.ServeJSON()
if sortField == "Direct" {
provider, err := c.GetProviderFromContext("Storage")
if err != nil {
c.ResponseError(err.Error())
return
}
prefix := sortOrder
resources, err := object.GetDirectResources(owner, user, provider, prefix, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(resources)
} else if limit == "" || page == "" {
resources, err := object.GetResources(owner, user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(resources)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetResourceCount(owner, user, field, value)))
resources := object.GetPaginationResources(owner, user, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetResourceCount(owner, user, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
resources, err := object.GetPaginationResources(owner, user, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(resources, paginator.Nums())
}
}
@ -63,17 +106,29 @@ func (c *ApiController) GetResources() {
// GetResource
// @Tag Resource API
// @Title GetResource
// @Description get resource
// @Param id query string true "The id ( owner/name ) of resource"
// @Success 200 {object} object.Resource The Response object
// @router /get-resource [get]
func (c *ApiController) GetResource() {
id := c.Input().Get("id")
c.Data["json"] = object.GetResource(id)
c.ServeJSON()
resource, err := object.GetResource(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(resource)
}
// UpdateResource
// @Tag Resource API
// @Title UpdateResource
// @Description get resource
// @Param id query string true "The id ( owner/name ) of resource"
// @Param resource body object.Resource true "The resource object"
// @Success 200 {object} controllers.Response Success or error
// @router /update-resource [post]
func (c *ApiController) UpdateResource() {
id := c.Input().Get("id")
@ -92,6 +147,8 @@ func (c *ApiController) UpdateResource() {
// AddResource
// @Tag Resource API
// @Title AddResource
// @Param resource body object.Resource true "Resource object"
// @Success 200 {object} controllers.Response Success or error
// @router /add-resource [post]
func (c *ApiController) AddResource() {
var resource object.Resource
@ -108,6 +165,8 @@ func (c *ApiController) AddResource() {
// DeleteResource
// @Tag Resource API
// @Title DeleteResource
// @Param resource body object.Resource true "Resource object"
// @Success 200 {object} controllers.Response Success or error
// @router /delete-resource [post]
func (c *ApiController) DeleteResource() {
var resource object.Resource
@ -117,10 +176,16 @@ func (c *ApiController) DeleteResource() {
return
}
provider, _, ok := c.GetProviderFromContext("Storage")
if !ok {
if resource.Provider != "" {
c.Input().Set("provider", resource.Provider)
}
c.Input().Set("fullFilePath", resource.Name)
provider, err := c.GetProviderFromContext("Storage")
if err != nil {
c.ResponseError(err.Error())
return
}
_, resource.Name = refineFullFilePath(resource.Name)
err = object.DeleteFile(provider, resource.Name, c.GetAcceptLanguage())
if err != nil {
@ -135,6 +200,16 @@ func (c *ApiController) DeleteResource() {
// UploadResource
// @Tag Resource API
// @Title UploadResource
// @Param owner query string true "Owner"
// @Param user query string true "User"
// @Param application query string true "Application"
// @Param tag query string false "Tag"
// @Param parent query string false "Parent"
// @Param fullFilePath query string true "Full File Path"
// @Param createdTime query string false "Created Time"
// @Param description query string false "Description"
// @Param file formData file true "Resource file"
// @Success 200 {object} object.Resource FileUrl, objectKey
// @router /upload-resource [post]
func (c *ApiController) UploadResource() {
owner := c.Input().Get("owner")
@ -165,27 +240,33 @@ func (c *ApiController) UploadResource() {
return
}
provider, _, ok := c.GetProviderFromContext("Storage")
if !ok {
provider, err := c.GetProviderFromContext("Storage")
if err != nil {
c.ResponseError(err.Error())
return
}
_, fullFilePath = refineFullFilePath(fullFilePath)
fileType := "unknown"
contentType := header.Header.Get("Content-Type")
fileType, _ = util.GetOwnerAndNameFromId(contentType)
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(contentType + "/")
if fileType != "image" && fileType != "video" {
ext := filepath.Ext(filename)
mimeType := mime.TypeByExtension(ext)
fileType, _ = util.GetOwnerAndNameFromId(mimeType)
fileType, _ = util.GetOwnerAndNameFromIdNoCheck(mimeType + "/")
}
if tag != "avatar" && tag != "termsOfUse" {
fullFilePath = object.GetTruncatedPath(provider, fullFilePath, 450)
if tag != "avatar" && tag != "termsOfUse" && !strings.HasPrefix(tag, "idCard") {
ext := filepath.Ext(filepath.Base(fullFilePath))
index := len(fullFilePath) - len(ext)
for i := 1; ; i++ {
_, objectKey := object.GetUploadFileUrl(provider, fullFilePath, true)
if object.GetResourceCount(owner, username, "name", objectKey) == 0 {
if count, err := object.GetResourceCount(owner, username, "name", objectKey); err != nil {
c.ResponseError(err.Error())
return
} else if count == 0 {
break
}
@ -194,12 +275,17 @@ func (c *ApiController) UploadResource() {
}
}
fileUrl, objectKey, err := object.UploadFileSafe(provider, fullFilePath, fileBuffer)
fileUrl, objectKey, err := object.UploadFileSafe(provider, fullFilePath, fileBuffer, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
if username == "Built-in-Untracked" {
c.ResponseOk(fileUrl, objectKey)
return
}
if createdTime == "" {
createdTime = util.GetCurrentTime()
}
@ -221,23 +307,84 @@ func (c *ApiController) UploadResource() {
Url: fileUrl,
Description: description,
}
object.AddOrUpdateResource(resource)
_, err = object.AddOrUpdateResource(resource)
if err != nil {
c.ResponseError(err.Error())
return
}
switch tag {
case "avatar":
user := object.GetUserNoCheck(util.GetId(owner, username))
user, err := object.GetUserNoCheck(util.GetId(owner, username))
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(c.T("resource:User is nil for tag: avatar"))
return
}
user.Avatar = fileUrl
object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
_, err = object.UpdateUser(user.GetId(), user, []string{"avatar"}, false)
if err != nil {
c.ResponseError(err.Error())
return
}
case "termsOfUse":
applicationId := fmt.Sprintf("admin/%s", parent)
app := object.GetApplication(applicationId)
app.TermsOfUse = fileUrl
object.UpdateApplication(applicationId, app)
user, err := object.GetUserNoCheck(util.GetId(owner, username))
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(owner, username)))
return
}
if !user.IsAdminUser() {
c.ResponseError(c.T("auth:Unauthorized operation"))
return
}
_, applicationId := util.GetOwnerAndNameFromIdNoCheck(strings.TrimSuffix(fullFilePath, ".html"))
applicationObj, err := object.GetApplication(applicationId)
if err != nil {
c.ResponseError(err.Error())
return
}
applicationObj.TermsOfUse = fileUrl
_, err = object.UpdateApplication(applicationId, applicationObj)
if err != nil {
c.ResponseError(err.Error())
return
}
case "idCardFront", "idCardBack", "idCardWithPerson":
user, err := object.GetUserNoCheck(util.GetId(owner, username))
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(c.T("resource:User is nil for tag: avatar"))
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)
if err != nil {
c.ResponseError(err.Error())
return
}
}
c.ResponseOk(fileUrl, objectKey)

View File

@ -37,13 +37,30 @@ func (c *ApiController) GetRoles() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetRoles(owner)
c.ServeJSON()
roles, err := object.GetRoles(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(roles)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetRoleCount(owner, field, value)))
roles := object.GetPaginationRoles(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetRoleCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
roles, err := object.GetPaginationRoles(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(roles, paginator.Nums())
}
}
@ -52,21 +69,26 @@ func (c *ApiController) GetRoles() {
// @Title GetRole
// @Tag Role API
// @Description get role
// @Param id query string true "The id of the role"
// @Param id query string true "The id ( owner/name ) of the role"
// @Success 200 {object} object.Role The Response object
// @router /get-role [get]
func (c *ApiController) GetRole() {
id := c.Input().Get("id")
c.Data["json"] = object.GetRole(id)
c.ServeJSON()
role, err := object.GetRole(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(role)
}
// UpdateRole
// @Title UpdateRole
// @Tag Role API
// @Description update role
// @Param id query string true "The id of the role"
// @Param id query string true "The id ( owner/name ) of the role"
// @Param body body object.Role true "The details of the role"
// @Success 200 {object} controllers.Response The Response object
// @router /update-role [post]

View File

@ -0,0 +1,54 @@
// 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 (
"fmt"
"os"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func (c *ApiController) UploadRoles() {
userId := c.GetSessionUsername()
owner, user := util.GetOwnerAndNameFromId(userId)
file, header, err := c.Ctx.Request.FormFile("file")
if err != nil {
c.ResponseError(err.Error())
return
}
fileId := fmt.Sprintf("%s_%s_%s", owner, user, util.RemoveExt(header.Filename))
path := util.GetUploadXlsxPath(fileId)
defer os.Remove(path)
err = saveFile(path, &file)
if err != nil {
c.ResponseError(err.Error())
return
}
affected, err := object.UploadRoles(owner, path)
if err != nil {
c.ResponseError(err.Error())
}
if affected {
c.ResponseOk()
} else {
c.ResponseError(c.T("general:Failed to import users"))
}
}

View File

@ -16,6 +16,7 @@ package controllers
import (
"fmt"
"net/http"
"github.com/casdoor/casdoor/object"
)
@ -23,12 +24,43 @@ import (
func (c *ApiController) GetSamlMeta() {
host := c.Ctx.Request.Host
paramApp := c.Input().Get("application")
application := object.GetApplication(paramApp)
application, err := object.GetApplication(paramApp)
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("saml:Application %s not found"), paramApp))
return
}
metadata, _ := object.GetSamlMeta(application, host)
enablePostBinding, err := c.GetBool("enablePostBinding", false)
if err != nil {
c.ResponseError(err.Error())
return
}
metadata, err := object.GetSamlMeta(application, host, enablePostBinding)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["xml"] = metadata
c.ServeXML()
}
func (c *ApiController) HandleSamlRedirect() {
host := c.Ctx.Request.Host
owner := c.Ctx.Input.Param(":owner")
application := c.Ctx.Input.Param(":application")
relayState := c.Input().Get("RelayState")
samlRequest := c.Input().Get("SAMLRequest")
targetURL := object.GetSamlRedirectAddress(owner, application, relayState, samlRequest, host)
c.Redirect(targetURL, http.StatusSeeOther)
}

View File

@ -1,4 +1,4 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
// 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.
@ -12,22 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package object
package controllers
func (syncer *Syncer) getUsers() []*User {
users := GetUsers(syncer.Organization)
return users
}
import (
"strings"
func (syncer *Syncer) getUserMap() ([]*User, map[string]*User, map[string]*User) {
users := syncer.getUsers()
"github.com/casdoor/casdoor/scim"
)
m1 := map[string]*User{}
m2 := map[string]*User{}
for _, user := range users {
m1[user.Id] = user
m2[user.Name] = user
func (c *RootController) HandleScim() {
_, ok := c.RequireAdmin()
if !ok {
return
}
return users, m1, m2
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

@ -20,17 +20,19 @@ package controllers
import (
"encoding/json"
"fmt"
"strings"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
type EmailForm struct {
Title string `json:"title"`
Content string `json:"content"`
Sender string `json:"sender"`
Receivers []string `json:"receivers"`
Provider string `json:"provider"`
Title string `json:"title"`
Content string `json:"content"`
Sender string `json:"sender"`
Receivers []string `json:"receivers"`
Provider string `json:"provider"`
ProviderObject object.Provider `json:"providerObject"`
}
type SmsForm struct {
@ -39,6 +41,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
@ -46,11 +52,15 @@ type SmsForm struct {
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param from body controllers.EmailForm true "Details of the email request"
// @Success 200 {object} Response object
// @router /api/send-email [post]
// @Success 200 {object} controllers.Response The Response object
// @router /send-email [post]
func (c *ApiController) SendEmail() {
var emailForm EmailForm
userId, ok := c.RequireSignedIn()
if !ok {
return
}
var emailForm EmailForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
if err != nil {
c.ResponseError(err.Error())
@ -60,19 +70,30 @@ func (c *ApiController) SendEmail() {
var provider *object.Provider
if emailForm.Provider != "" {
// called by frontend's TestEmailWidget, provider name is set by frontend
provider = object.GetProvider(util.GetId("admin", emailForm.Provider))
provider, err = object.GetProvider(util.GetId("admin", emailForm.Provider))
if err != nil {
c.ResponseError(err.Error())
return
}
} else {
// called by Casdoor SDK via Client ID & Client Secret, so the used Email provider will be the application' Email provider or the default Email provider
var ok bool
provider, _, ok = c.GetProviderFromContext("Email")
if !ok {
provider, err = c.GetProviderFromContext("Email")
if err != nil {
c.ResponseError(err.Error())
return
}
}
if emailForm.ProviderObject.Name != "" {
if emailForm.ProviderObject.ClientSecret == "***" {
emailForm.ProviderObject.ClientSecret = provider.ClientSecret
}
provider = &emailForm.ProviderObject
}
// when receiver is the reserved keyword: "TestSmtpServer", it means to test the SMTP server instead of sending a real Email
if len(emailForm.Receivers) == 1 && emailForm.Receivers[0] == "TestSmtpServer" {
err := object.DailSmtpServer(provider)
err = object.TestSmtpServer(provider)
if err != nil {
c.ResponseError(err.Error())
return
@ -80,7 +101,7 @@ func (c *ApiController) SendEmail() {
c.ResponseOk()
}
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
if util.IsStringsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
c.ResponseError(fmt.Sprintf(c.T("service:Empty parameters for emailForm: %v"), emailForm))
return
}
@ -97,8 +118,30 @@ func (c *ApiController) SendEmail() {
return
}
content := emailForm.Content
if content == "" {
content = provider.Content
}
code := "123456"
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content = strings.Replace(content, "%s", code, 1)
userString := "Hi"
if !object.IsAppUser(userId) {
var user *object.User
user, err = object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user != nil {
userString = user.GetFriendlyName()
}
}
content = strings.Replace(content, "%{user.friendlyName}", userString, 1)
for _, receiver := range emailForm.Receivers {
err = object.SendEmail(provider, emailForm.Title, emailForm.Content, receiver, emailForm.Sender)
err = object.SendEmail(provider, emailForm.Title, content, receiver, emailForm.Sender)
if err != nil {
c.ResponseError(err.Error())
return
@ -115,34 +158,28 @@ func (c *ApiController) SendEmail() {
// @Param clientId query string true "The clientId of the application"
// @Param clientSecret query string true "The clientSecret of the application"
// @Param from body controllers.SmsForm true "Details of the sms request"
// @Success 200 {object} Response object
// @router /api/send-sms [post]
// @Success 200 {object} controllers.Response The Response object
// @router /send-sms [post]
func (c *ApiController) SendSms() {
provider, _, ok := c.GetProviderFromContext("SMS")
if !ok {
return
}
var smsForm SmsForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
provider, err := c.GetProviderFromContext("SMS")
if err != nil {
c.ResponseError(err.Error())
return
}
org := object.GetOrganization(smsForm.OrgId)
var invalidReceivers []string
for idx, receiver := range smsForm.Receivers {
if !util.IsPhoneCnValid(receiver) {
invalidReceivers = append(invalidReceivers, receiver)
} else {
smsForm.Receivers[idx] = fmt.Sprintf("+%s%s", org.PhonePrefix, receiver)
}
var smsForm SmsForm
err = json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
if err != nil {
c.ResponseError(err.Error())
return
}
if len(invalidReceivers) != 0 {
c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), invalidReceivers))
return
if provider.Type != "Custom HTTP SMS" {
invalidReceivers := getInvalidSmsReceivers(smsForm)
if len(invalidReceivers) != 0 {
c.ResponseError(fmt.Sprintf(c.T("service:Invalid phone receivers: %s"), strings.Join(invalidReceivers, ", ")))
return
}
}
err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...)
@ -153,3 +190,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} controllers.Response The Response object
// @router /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

@ -22,29 +22,10 @@ import (
"github.com/casdoor/casdoor/util"
)
// DeleteSession
// @Title DeleteSession
// @Tag Session API
// @Description Delete session by userId
// @Param ID query string true "The ID(owner/name) of user."
// @Success 200 {array} string The Response object
// @router /delete-session [post]
func (c *ApiController) DeleteSession() {
var session object.Session
err := json.Unmarshal(c.Ctx.Input.RequestBody, &session)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteSession(util.GetId(session.Owner, session.Name)))
c.ServeJSON()
}
// GetSessions
// @Title GetSessions
// @Tag Session API
// @Description Get organization user sessions
// @Description Get organization user sessions.
// @Param owner query string true "The organization name"
// @Success 200 {array} string The Response object
// @router /get-sessions [get]
@ -56,13 +37,127 @@ func (c *ApiController) GetSessions() {
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
owner := c.Input().Get("owner")
if limit == "" || page == "" {
c.Data["json"] = object.GetSessions(owner)
c.ServeJSON()
sessions, err := object.GetSessions(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(sessions)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSessionCount(owner, field, value)))
sessions := object.GetPaginationSessions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetSessionCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
sessions, err := object.GetPaginationSessions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(sessions, paginator.Nums())
}
}
// GetSingleSession
// @Title GetSingleSession
// @Tag Session API
// @Description Get session for one user in one application.
// @Param id query string true "The id(organization/application/user) of session"
// @Success 200 {array} string The Response object
// @router /get-session [get]
func (c *ApiController) GetSingleSession() {
id := c.Input().Get("sessionPkId")
session, err := object.GetSingleSession(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(session)
}
// UpdateSession
// @Title UpdateSession
// @Tag Session API
// @Description Update session for one user in one application.
// @Param id query string true "The id(organization/application/user) of session"
// @Success 200 {array} string The Response object
// @router /update-session [post]
func (c *ApiController) UpdateSession() {
var session object.Session
err := json.Unmarshal(c.Ctx.Input.RequestBody, &session)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateSession(util.GetSessionId(session.Owner, session.Name, session.Application), &session))
c.ServeJSON()
}
// AddSession
// @Title AddSession
// @Tag Session API
// @Description Add session for one user in one application. If there are other existing sessions, join the session into the list.
// @Param id query string true "The id(organization/application/user) of session"
// @Param sessionId query string true "sessionId to be added"
// @Success 200 {array} string The Response object
// @router /add-session [post]
func (c *ApiController) AddSession() {
var session object.Session
err := json.Unmarshal(c.Ctx.Input.RequestBody, &session)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddSession(&session))
c.ServeJSON()
}
// DeleteSession
// @Title DeleteSession
// @Tag Session API
// @Description Delete session for one user in one application.
// @Param id query string true "The id(organization/application/user) of session"
// @Success 200 {array} string The Response object
// @router /delete-session [post]
func (c *ApiController) DeleteSession() {
var session object.Session
err := json.Unmarshal(c.Ctx.Input.RequestBody, &session)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteSession(util.GetSessionId(session.Owner, session.Name, session.Application)))
c.ServeJSON()
}
// IsSessionDuplicated
// @Title IsSessionDuplicated
// @Tag Session API
// @Description Check if there are other different sessions for one user in one application.
// @Param id query string true "The id(organization/application/user) of session"
// @Param sessionId query string true "sessionId to be checked"
// @Success 200 {array} string The Response object
// @router /is-session-duplicated [get]
func (c *ApiController) IsSessionDuplicated() {
id := c.Input().Get("sessionPkId")
sessionId := c.Input().Get("sessionId")
isUserSessionDuplicated, err := object.IsSessionDuplicated(id, sessionId)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(isUserSessionDuplicated)
}

145
controllers/subscription.go Normal file
View File

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

View File

@ -37,13 +37,31 @@ func (c *ApiController) GetSyncers() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
c.Data["json"] = object.GetSyncers(owner)
c.ServeJSON()
syncers, err := object.GetMaskedSyncers(object.GetOrganizationSyncers(owner, organization))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(syncers)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetSyncerCount(owner, field, value)))
syncers := object.GetPaginationSyncers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetSyncerCount(owner, organization, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
syncers, err := object.GetMaskedSyncers(object.GetPaginationSyncers(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(syncers, paginator.Nums())
}
}
@ -52,21 +70,26 @@ func (c *ApiController) GetSyncers() {
// @Title GetSyncer
// @Tag Syncer API
// @Description get syncer
// @Param id query string true "The id of the syncer"
// @Param id query string true "The id ( owner/name ) of the syncer"
// @Success 200 {object} object.Syncer The Response object
// @router /get-syncer [get]
func (c *ApiController) GetSyncer() {
id := c.Input().Get("id")
c.Data["json"] = object.GetSyncer(id)
c.ServeJSON()
syncer, err := object.GetMaskedSyncer(object.GetSyncer(id))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(syncer)
}
// UpdateSyncer
// @Title UpdateSyncer
// @Tag Syncer API
// @Description update syncer
// @Param id query string true "The id of the syncer"
// @Param id query string true "The id ( owner/name ) of the syncer"
// @Param body body object.Syncer true "The details of the syncer"
// @Success 200 {object} controllers.Response The Response object
// @router /update-syncer [post]
@ -131,9 +154,34 @@ func (c *ApiController) DeleteSyncer() {
// @router /run-syncer [get]
func (c *ApiController) RunSyncer() {
id := c.Input().Get("id")
syncer := object.GetSyncer(id)
syncer, err := object.GetSyncer(id)
if err != nil {
c.ResponseError(err.Error())
return
}
object.RunSyncer(syncer)
err = object.RunSyncer(syncer)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}
func (c *ApiController) TestSyncerDb() {
var syncer object.Syncer
err := json.Unmarshal(c.Ctx.Input.RequestBody, &syncer)
if err != nil {
c.ResponseError(err.Error())
return
}
err = object.TestSyncerDb(syncer)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}

View File

@ -15,68 +15,64 @@
package controllers
import (
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
type SystemInfo struct {
MemoryUsed uint64 `json:"memory_used"`
MemoryTotal uint64 `json:"memory_total"`
CpuUsage []float64 `json:"cpu_usage"`
}
// GetSystemInfo
// @Title GetSystemInfo
// @Tag System API
// @Description get user's system info
// @Param id query string true "The id of the user"
// @Success 200 {object} object.SystemInfo The Response object
// @Description get system info like CPU and memory usage
// @Success 200 {object} util.SystemInfo The Response object
// @router /get-system-info [get]
func (c *ApiController) GetSystemInfo() {
id := c.GetString("id")
if id == "" {
id = c.GetSessionUsername()
}
user := object.GetUser(id)
if user == nil || !user.IsGlobalAdmin {
c.ResponseError(c.T("auth:Unauthorized operation"))
_, ok := c.RequireAdmin()
if !ok {
return
}
cpuUsage, err := util.GetCpuUsage()
systemInfo, err := util.GetSystemInfo()
if err != nil {
c.ResponseError(err.Error())
return
}
memoryUsed, memoryTotal, err := util.GetMemoryUsage()
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = SystemInfo{
CpuUsage: cpuUsage,
MemoryUsed: memoryUsed,
MemoryTotal: memoryTotal,
}
c.ServeJSON()
c.ResponseOk(systemInfo)
}
// GitRepoVersion
// @Title GitRepoVersion
// GetVersionInfo
// @Title GetVersionInfo
// @Tag System API
// @Description get local github repo's latest release version info
// @Success 200 {string} local latest version hash of casdoor
// @router /get-release [get]
func (c *ApiController) GitRepoVersion() {
version, err := util.GetGitRepoVersion()
// @Description get version info like Casdoor release version and commit ID
// @Success 200 {object} util.VersionInfo The Response object
// @router /get-version-info [get]
func (c *ApiController) GetVersionInfo() {
errInfo := ""
versionInfo, err := util.GetVersionInfo()
if err != nil {
c.ResponseError(err.Error())
errInfo = "Git error: " + err.Error()
}
if versionInfo.Version != "" {
c.ResponseOk(versionInfo)
return
}
c.Data["json"] = version
c.ServeJSON()
versionInfo, err = util.GetVersionInfoFromFile()
if err != nil {
errInfo = errInfo + ", File error: " + err.Error()
c.ResponseError(errInfo)
return
}
c.ResponseOk(versionInfo)
}
// Health
// @Title Health
// @Tag System API
// @Description check if the system is live
// @Success 200 {object} controllers.Response The Response object
// @router /health [get]
func (c *ApiController) Health() {
c.ResponseOk()
}

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"time"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -39,13 +40,30 @@ func (c *ApiController) GetTokens() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
c.Data["json"] = object.GetTokens(owner)
c.ServeJSON()
token, err := object.GetTokens(owner, organization)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(token)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetTokenCount(owner, field, value)))
tokens := object.GetPaginationTokens(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetTokenCount(owner, organization, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
tokens, err := object.GetPaginationTokens(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(tokens, paginator.Nums())
}
}
@ -54,21 +72,25 @@ func (c *ApiController) GetTokens() {
// @Title GetToken
// @Tag Token API
// @Description get token
// @Param id query string true "The id of token"
// @Param id query string true "The id ( owner/name ) of token"
// @Success 200 {object} object.Token The Response object
// @router /get-token [get]
func (c *ApiController) GetToken() {
id := c.Input().Get("id")
token, err := object.GetToken(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = object.GetToken(id)
c.ServeJSON()
c.ResponseOk(token)
}
// UpdateToken
// @Title UpdateToken
// @Tag Token API
// @Description update token
// @Param id query string true "The id of token"
// @Param id query string true "The id ( owner/name ) of token"
// @Param body body object.Token true "Details of the token"
// @Success 200 {object} controllers.Response The Response object
// @router /update-token [post]
@ -124,40 +146,6 @@ func (c *ApiController) DeleteToken() {
c.ServeJSON()
}
// GetOAuthCode
// @Title GetOAuthCode
// @Tag Token API
// @Description get OAuth code
// @Param user_id query string true "The id of user"
// @Param client_id query string true "OAuth client id"
// @Param response_type query string true "OAuth response type"
// @Param redirect_uri query string true "OAuth redirect URI"
// @Param scope query string true "OAuth scope"
// @Param state query string true "OAuth state"
// @Success 200 {object} object.TokenWrapper The Response object
// @router /login/oauth/code [post]
func (c *ApiController) GetOAuthCode() {
userId := c.Input().Get("user_id")
clientId := c.Input().Get("client_id")
responseType := c.Input().Get("response_type")
redirectUri := c.Input().Get("redirect_uri")
scope := c.Input().Get("scope")
state := c.Input().Get("state")
nonce := c.Input().Get("nonce")
challengeMethod := c.Input().Get("code_challenge_method")
codeChallenge := c.Input().Get("code_challenge")
if challengeMethod != "S256" && challengeMethod != "null" && challengeMethod != "" {
c.ResponseError(c.T("auth:Challenge method should be S256"))
return
}
host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthCode(userId, clientId, responseType, redirectUri, scope, state, nonce, codeChallenge, host, c.GetAcceptLanguage())
c.ServeJSON()
}
// GetOAuthToken
// @Title GetOAuthToken
// @Tag Token API
@ -171,39 +159,116 @@ func (c *ApiController) GetOAuthCode() {
// @Success 401 {object} object.TokenError The Response object
// @router /login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type")
clientId := c.Input().Get("client_id")
clientSecret := c.Input().Get("client_secret")
grantType := c.Input().Get("grant_type")
code := c.Input().Get("code")
verifier := c.Input().Get("code_verifier")
scope := c.Input().Get("scope")
nonce := c.Input().Get("nonce")
username := c.Input().Get("username")
password := c.Input().Get("password")
tag := c.Input().Get("tag")
avatar := c.Input().Get("avatar")
refreshToken := c.Input().Get("refresh_token")
deviceCode := c.Input().Get("device_code")
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
if len(c.Ctx.Input.RequestBody) != 0 && grantType != "urn:ietf:params:oauth:grant-type:device_code" {
// If clientId is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
code = tokenRequest.Code
verifier = tokenRequest.Verifier
scope = tokenRequest.Scope
username = tokenRequest.Username
password = tokenRequest.Password
tag = tokenRequest.Tag
avatar = tokenRequest.Avatar
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
if err == nil {
if clientId == "" {
clientId = tokenRequest.ClientId
}
if clientSecret == "" {
clientSecret = tokenRequest.ClientSecret
}
if grantType == "" {
grantType = tokenRequest.GrantType
}
if code == "" {
code = tokenRequest.Code
}
if verifier == "" {
verifier = tokenRequest.Verifier
}
if scope == "" {
scope = tokenRequest.Scope
}
if nonce == "" {
nonce = tokenRequest.Nonce
}
if username == "" {
username = tokenRequest.Username
}
if password == "" {
password = tokenRequest.Password
}
if tag == "" {
tag = tokenRequest.Tag
}
if avatar == "" {
avatar = tokenRequest.Avatar
}
if refreshToken == "" {
refreshToken = tokenRequest.RefreshToken
}
}
}
host := c.Ctx.Request.Host
c.Data["json"] = object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, tag, avatar, c.GetAcceptLanguage())
if deviceCode != "" {
deviceAuthCache, ok := object.DeviceAuthMap.Load(deviceCode)
if !ok {
c.Data["json"] = &object.TokenError{
Error: "expired_token",
ErrorDescription: "token is expired",
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
c.SetTokenErrorHttpStatus()
return
}
deviceAuthCacheCast := deviceAuthCache.(object.DeviceAuthCache)
if !deviceAuthCacheCast.UserSignIn {
c.Data["json"] = &object.TokenError{
Error: "authorization_pending",
ErrorDescription: "authorization pending",
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
c.SetTokenErrorHttpStatus()
return
}
if deviceAuthCacheCast.RequestAt.Add(time.Second * 120).Before(time.Now()) {
c.Data["json"] = &object.TokenError{
Error: "expired_token",
ErrorDescription: "token is expired",
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
c.SetTokenErrorHttpStatus()
return
}
object.DeviceAuthMap.Delete(deviceCode)
username = deviceAuthCacheCast.UserName
}
host := c.Ctx.Request.Host
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = token
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
@ -241,13 +306,28 @@ func (c *ApiController) RefreshToken() {
}
}
c.Data["json"] = object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
refreshToken2, err := object.RefreshToken(grantType, refreshToken, scope, clientId, clientSecret, host)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = refreshToken2
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
func (c *ApiController) ResponseTokenError(errorMsg string) {
c.Data["json"] = &object.TokenError{
Error: errorMsg,
}
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}
// IntrospectToken
// @Title IntrospectToken
// @Tag Login API
// @Description The introspection endpoint is an OAuth 2.0 endpoint that takes a
// parameter representing an OAuth 2.0 token and returns a JSON document
// representing the meta information surrounding the
@ -267,53 +347,122 @@ func (c *ApiController) IntrospectToken() {
clientId = c.Input().Get("client_id")
clientSecret = c.Input().Get("client_secret")
if clientId == "" || clientSecret == "" {
c.ResponseError(c.T("token:Empty clientId or clientSecret"))
c.Data["json"] = &object.TokenError{
Error: object.InvalidRequest,
}
c.SetTokenErrorHttpStatus()
c.ResponseTokenError(object.InvalidRequest)
return
}
}
application, err := object.GetApplicationByClientId(clientId)
if err != nil {
c.ResponseTokenError(err.Error())
return
}
if application == nil || application.ClientSecret != clientSecret {
c.ResponseTokenError(c.T("token:Invalid application or wrong clientSecret"))
return
}
respondWithInactiveToken := func() {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
}
tokenTypeHint := c.Input().Get("token_type_hint")
var token *object.Token
if tokenTypeHint != "" {
token, err = object.GetTokenByTokenValue(tokenValue, tokenTypeHint)
if err != nil {
c.ResponseTokenError(err.Error())
return
}
if token == nil || token.ExpiresIn <= 0 {
respondWithInactiveToken()
return
}
if token.ExpiresIn <= 0 {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
}
application := object.GetApplicationByClientId(clientId)
if application == nil || application.ClientSecret != clientSecret {
c.ResponseError(c.T("token:Invalid application or wrong clientSecret"))
c.Data["json"] = &object.TokenError{
Error: object.InvalidClient,
var introspectionResponse object.IntrospectionResponse
if application.TokenFormat == "JWT-Standard" {
jwtToken, err := object.ParseStandardJwtTokenByApplication(tokenValue, application)
if err != nil {
// and token revoked case. but we not implement
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
// refs: https://tools.ietf.org/html/rfc7009
respondWithInactiveToken()
return
}
introspectionResponse = object.IntrospectionResponse{
Active: true,
Scope: jwtToken.Scope,
ClientId: clientId,
Username: jwtToken.Name,
TokenType: jwtToken.TokenType,
Exp: jwtToken.ExpiresAt.Unix(),
Iat: jwtToken.IssuedAt.Unix(),
Nbf: jwtToken.NotBefore.Unix(),
Sub: jwtToken.Subject,
Aud: jwtToken.Audience,
Iss: jwtToken.Issuer,
Jti: jwtToken.ID,
}
} else {
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
if err != nil {
// and token revoked case. but we not implement
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
// refs: https://tools.ietf.org/html/rfc7009
respondWithInactiveToken()
return
}
introspectionResponse = object.IntrospectionResponse{
Active: true,
ClientId: clientId,
Exp: jwtToken.ExpiresAt.Unix(),
Iat: jwtToken.IssuedAt.Unix(),
Nbf: jwtToken.NotBefore.Unix(),
Sub: jwtToken.Subject,
Aud: jwtToken.Audience,
Iss: jwtToken.Issuer,
Jti: jwtToken.ID,
}
if jwtToken.Scope != "" {
introspectionResponse.Scope = jwtToken.Scope
}
if jwtToken.Name != "" {
introspectionResponse.Username = jwtToken.Name
}
if jwtToken.TokenType != "" {
introspectionResponse.TokenType = jwtToken.TokenType
}
c.SetTokenErrorHttpStatus()
return
}
token := object.GetTokenByTokenAndApplication(tokenValue, application.Name)
if token == nil {
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
if err != nil || jwtToken.Valid() != nil {
// and token revoked case. but we not implement
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
// refs: https://tools.ietf.org/html/rfc7009
c.Data["json"] = &object.IntrospectionResponse{Active: false}
c.ServeJSON()
return
}
c.Data["json"] = &object.IntrospectionResponse{
Active: true,
Scope: jwtToken.Scope,
ClientId: clientId,
Username: token.User,
TokenType: token.TokenType,
Exp: jwtToken.ExpiresAt.Unix(),
Iat: jwtToken.IssuedAt.Unix(),
Nbf: jwtToken.NotBefore.Unix(),
Sub: jwtToken.Subject,
Aud: jwtToken.Audience,
Iss: jwtToken.Issuer,
Jti: jwtToken.Id,
if tokenTypeHint == "" {
token, err = object.GetTokenByTokenValue(tokenValue, introspectionResponse.TokenType)
if err != nil {
c.ResponseTokenError(err.Error())
return
}
if token == nil || token.ExpiresIn <= 0 {
respondWithInactiveToken()
return
}
}
if token != nil {
introspectionResponse.TokenType = token.TokenType
}
c.Data["json"] = introspectionResponse
c.ServeJSON()
}

167
controllers/transaction.go Normal file
View File

@ -0,0 +1,167 @@
// Copyright 2024 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"
)
// GetTransactions
// @Title GetTransactions
// @Tag Transaction API
// @Description get transactions
// @Param owner query string true "The owner of transactions"
// @Success 200 {array} object.Transaction The Response object
// @router /get-transactions [get]
func (c *ApiController) GetTransactions() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
transactions, err := object.GetTransactions(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(transactions)
} else {
limit := util.ParseInt(limit)
count, err := object.GetTransactionCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
transactions, err := object.GetPaginationTransactions(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(transactions, paginator.Nums())
}
}
// GetUserTransactions
// @Title GetUserTransaction
// @Tag Transaction API
// @Description get transactions for a user
// @Param owner query string true "The owner of transactions"
// @Param organization query string true "The organization of the user"
// @Param user query string true "The username of the user"
// @Success 200 {array} object.Transaction The Response object
// @router /get-user-transactions [get]
func (c *ApiController) GetUserTransactions() {
owner := c.Input().Get("owner")
user := c.Input().Get("user")
transactions, err := object.GetUserTransactions(owner, user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(transactions)
}
// GetTransaction
// @Title GetTransaction
// @Tag Transaction API
// @Description get transaction
// @Param id query string true "The id ( owner/name ) of the transaction"
// @Success 200 {object} object.Transaction The Response object
// @router /get-transaction [get]
func (c *ApiController) GetTransaction() {
id := c.Input().Get("id")
transaction, err := object.GetTransaction(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(transaction)
}
// UpdateTransaction
// @Title UpdateTransaction
// @Tag Transaction API
// @Description update transaction
// @Param id query string true "The id ( owner/name ) of the transaction"
// @Param body body object.Transaction true "The details of the transaction"
// @Success 200 {object} controllers.Response The Response object
// @router /update-transaction [post]
func (c *ApiController) UpdateTransaction() {
id := c.Input().Get("id")
var transaction object.Transaction
err := json.Unmarshal(c.Ctx.Input.RequestBody, &transaction)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.UpdateTransaction(id, &transaction))
c.ServeJSON()
}
// AddTransaction
// @Title AddTransaction
// @Tag Transaction API
// @Description add transaction
// @Param body body object.Transaction true "The details of the transaction"
// @Success 200 {object} controllers.Response The Response object
// @router /add-transaction [post]
func (c *ApiController) AddTransaction() {
var transaction object.Transaction
err := json.Unmarshal(c.Ctx.Input.RequestBody, &transaction)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.AddTransaction(&transaction))
c.ServeJSON()
}
// DeleteTransaction
// @Title DeleteTransaction
// @Tag Transaction API
// @Description delete transaction
// @Param body body object.Transaction true "The details of the transaction"
// @Success 200 {object} controllers.Response The Response object
// @router /delete-transaction [post]
func (c *ApiController) DeleteTransaction() {
var transaction object.Transaction
err := json.Unmarshal(c.Ctx.Input.RequestBody, &transaction)
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = wrapActionResponse(object.DeleteTransaction(&transaction))
c.ServeJSON()
}

View File

@ -15,12 +15,13 @@
package controllers
type TokenRequest struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
GrantType string `json:"grant_type"`
Code string `json:"code"`
Verifier string `json:"code_verifier"`
Scope string `json:"scope"`
Nonce string `json:"nonce"`
Username string `json:"username"`
Password string `json:"password"`
Tag string `json:"tag"`

View File

@ -20,6 +20,7 @@ import (
"strings"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -37,14 +38,36 @@ func (c *ApiController) GetGlobalUsers() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedUsers(object.GetGlobalUsers())
c.ServeJSON()
users, err := object.GetMaskedUsers(object.GetGlobalUsers())
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(users)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetGlobalUserCount(field, value)))
users := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
users = object.GetMaskedUsers(users)
count, err := object.GetGlobalUserCount(field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
users, err := object.GetPaginationGlobalUsers(paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
users, err = object.GetMaskedUsers(users)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(users, paginator.Nums())
}
}
@ -58,20 +81,53 @@ func (c *ApiController) GetGlobalUsers() {
// @router /get-users [get]
func (c *ApiController) GetUsers() {
owner := c.Input().Get("owner")
groupName := c.Input().Get("groupName")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
c.Data["json"] = object.GetMaskedUsers(object.GetUsers(owner))
c.ServeJSON()
if groupName != "" {
users, err := object.GetMaskedUsers(object.GetGroupUsers(util.GetId(owner, groupName)))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(users)
return
}
users, err := object.GetMaskedUsers(object.GetUsers(owner))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(users)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetUserCount(owner, field, value)))
users := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
users = object.GetMaskedUsers(users)
count, err := object.GetUserCount(owner, field, value, groupName)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
users, err := object.GetPaginationUsers(owner, paginator.Offset(), limit, field, value, sortField, sortOrder, groupName)
if err != nil {
c.ResponseError(err.Error())
return
}
users, err = object.GetMaskedUsers(users)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(users, paginator.Nums())
}
}
@ -80,10 +136,11 @@ func (c *ApiController) GetUsers() {
// @Title GetUser
// @Tag User API
// @Description get user
// @Param id query string true "The id of the user"
// @Param id query string false "The id ( owner/name ) of the user"
// @Param owner query string false "The owner of the user"
// @Param email query string false "The email of the user"
// @Param phone query string false "The phone of the user"
// @Param userId query string false "The userId of the user"
// @Success 200 {object} object.User The Response object
// @router /get-user [get]
func (c *ApiController) GetUser() {
@ -91,45 +148,111 @@ func (c *ApiController) GetUser() {
email := c.Input().Get("email")
phone := c.Input().Get("phone")
userId := c.Input().Get("userId")
owner := c.Input().Get("owner")
if owner == "" {
owner, _ = util.GetOwnerAndNameFromId(id)
var err error
var userFromUserId *object.User
if userId != "" && owner != "" {
userFromUserId, err = object.GetUserByUserId(owner, userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if userFromUserId == nil {
c.ResponseOk(nil)
return
}
id = util.GetId(userFromUserId.Owner, userFromUserId.Name)
}
organization := object.GetOrganization(fmt.Sprintf("%s/%s", "admin", owner))
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
hasPermission, err := object.CheckUserPermission(requestUserId, id, owner, false, c.GetAcceptLanguage())
if !hasPermission {
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)
}
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 {
c.ResponseError(err.Error())
return
}
var organization *object.Organization
if user != nil {
organization, err = object.GetOrganizationByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
if organization == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The organization: %s does not exist"), owner))
return
}
if !organization.IsProfilePublic {
requestUserId := c.GetSessionUsername()
var hasPermission bool
hasPermission, err = object.CheckUserPermission(requestUserId, user.GetId(), false, c.GetAcceptLanguage())
if !hasPermission {
c.ResponseError(err.Error())
return
}
}
}
if user != nil {
user.MultiFactorAuths = object.GetAllMfaProps(user, true)
}
err = object.ExtendUserWithRolesAndPermissions(user)
if err != nil {
c.ResponseError(err.Error())
return
}
isAdminOrSelf := c.IsAdminOrSelf(user)
user, err = object.GetMaskedUser(user, isAdminOrSelf)
if err != nil {
c.ResponseError(err.Error())
return
}
if organization != nil && user != nil {
user, err = object.GetFilteredUser(user, c.IsAdmin(), c.IsAdminOrSelf(user), organization.AccountItems)
if err != nil {
c.ResponseError(err.Error())
return
}
}
var user *object.User
switch {
case email != "":
user = object.GetUserByEmail(owner, email)
case phone != "":
user = object.GetUserByPhone(owner, phone)
case userId != "":
user = object.GetUserByUserId(owner, userId)
default:
user = object.GetUser(id)
}
object.ExtendUserWithRolesAndPermissions(user)
c.Data["json"] = object.GetMaskedUser(user)
c.ServeJSON()
c.ResponseOk(user)
}
// UpdateUser
// @Title UpdateUser
// @Tag User API
// @Description update user
// @Param id query string true "The id of the user"
// @Param id query string true "The id ( owner/name ) of the user"
// @Param body body object.User true "The details of the user"
// @Success 200 {object} controllers.Response The Response object
// @router /update-user [post]
@ -137,10 +260,6 @@ func (c *ApiController) UpdateUser() {
id := c.Input().Get("id")
columnsStr := c.Input().Get("columns")
if id == "" {
id = c.GetSessionUsername()
}
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
@ -148,26 +267,73 @@ func (c *ApiController) UpdateUser() {
return
}
if msg := object.CheckUpdateUser(object.GetUser(id), &user, c.GetAcceptLanguage()); msg != "" {
if id == "" {
id = c.GetSessionUsername()
if id == "" {
c.ResponseError(c.T("general:Missing parameter"))
return
}
}
oldUser, err := object.GetUser(id)
if err != nil {
c.ResponseError(err.Error())
return
}
if oldUser == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), id))
return
}
if oldUser.Owner == "built-in" && oldUser.Name == "admin" && (user.Owner != "built-in" || user.Name != "admin") {
c.ResponseError(c.T("auth:Unauthorized operation"))
return
}
if user.MfaEmailEnabled && user.Email == "" {
c.ResponseError(c.T("user:MFA email is enabled but email is empty"))
return
}
if user.MfaPhoneEnabled && user.Phone == "" {
c.ResponseError(c.T("user:MFA phone is enabled but phone number is empty"))
return
}
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
c.ResponseError(msg)
return
}
isUsernameLowered := conf.GetConfigBool("isUsernameLowered")
if isUsernameLowered {
user.Name = strings.ToLower(user.Name)
}
isAdmin := c.IsAdmin()
allowDisplayNameEmpty := c.Input().Get("allowEmpty") != ""
if pass, err := object.CheckPermissionForUpdateUser(oldUser, &user, isAdmin, allowDisplayNameEmpty, c.GetAcceptLanguage()); !pass {
c.ResponseError(err)
return
}
columns := []string{}
if columnsStr != "" {
columns = strings.Split(columnsStr, ",")
}
isGlobalAdmin := c.IsGlobalAdmin()
if pass, err := checkPermissionForUpdateUser(id, user, c); !pass {
c.ResponseError(err)
affected, err := object.UpdateUser(id, &user, columns, isAdmin)
if err != nil {
c.ResponseError(err.Error())
return
}
affected := object.UpdateUser(id, &user, columns, isGlobalAdmin)
if affected {
object.UpdateUserToOriginalDatabase(&user)
err = object.UpdateUserToOriginalDatabase(&user)
if err != nil {
c.ResponseError(err.Error())
return
}
}
c.Data["json"] = wrapActionResponse(affected)
@ -189,19 +355,19 @@ func (c *ApiController) AddUser() {
return
}
count := object.GetUserCount("", "", "")
if err := checkQuotaForUser(count); err != nil {
if err := checkQuotaForUser(); err != nil {
c.ResponseError(err.Error())
return
}
msg := object.CheckUsername(user.Name, c.GetAcceptLanguage())
emptyUser := object.User{}
msg := object.CheckUpdateUser(&emptyUser, &user, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
return
}
c.Data["json"] = wrapActionResponse(object.AddUser(&user))
c.Data["json"] = wrapActionResponse(object.AddUser(&user, c.GetAcceptLanguage()))
c.ServeJSON()
}
@ -220,6 +386,11 @@ func (c *ApiController) DeleteUser() {
return
}
if user.Owner == "built-in" && user.Name == "admin" {
c.ResponseError(c.T("auth:Unauthorized operation"))
return
}
c.Data["json"] = wrapActionResponse(object.DeleteUser(&user))
c.ServeJSON()
}
@ -231,24 +402,31 @@ func (c *ApiController) DeleteUser() {
// @Param username formData string true "The username of the user"
// @Param organization formData string true "The organization of the user"
// @Success 200 {object} controllers.Response The Response object
// @router /get-email-and-phone [post]
// @router /get-email-and-phone [get]
func (c *ApiController) GetEmailAndPhone() {
var form RequestForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &form)
organization := c.Ctx.Request.Form.Get("organization")
username := c.Ctx.Request.Form.Get("username")
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
if enableErrorMask2 {
c.ResponseError("Error")
return
}
user, err := object.GetUserByFields(organization, username)
if err != nil {
c.ResponseError(err.Error())
return
}
user := object.GetUserByFields(form.Organization, form.Username)
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(form.Organization, form.Username)))
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(organization, username)))
return
}
respUser := object.User{Name: user.Name}
var contentType string
switch form.Username {
switch username {
case user.Email:
contentType = "email"
respUser.Email = user.Email
@ -279,46 +457,145 @@ func (c *ApiController) SetPassword() {
userName := c.Ctx.Request.Form.Get("userName")
oldPassword := c.Ctx.Request.Form.Get("oldPassword")
newPassword := c.Ctx.Request.Form.Get("newPassword")
code := c.Ctx.Request.Form.Get("code")
requestUserId := c.GetSessionUsername()
userId := fmt.Sprintf("%s/%s", userOwner, userName)
hasPermission, err := object.CheckUserPermission(requestUserId, userId, userOwner, true, c.GetAcceptLanguage())
if !hasPermission {
c.ResponseError(err.Error())
return
}
targetUser := object.GetUser(userId)
if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
return
}
}
// if userOwner == "built-in" && userName == "admin" {
// c.ResponseError(c.T("auth:Unauthorized operation"))
// return
// }
if strings.Contains(newPassword, " ") {
c.ResponseError(c.T("user:New password cannot contain blank space."))
return
}
if len(newPassword) <= 5 {
c.ResponseError(c.T("user:New password must have at least 6 characters"))
userId := util.GetId(userOwner, userName)
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return
}
requestUserId := c.GetSessionUsername()
if requestUserId == "" && code == "" {
c.ResponseError(c.T("general:Please login first"), "Please login first")
return
} else if code == "" {
hasPermission, err := object.CheckUserPermission(requestUserId, userId, true, c.GetAcceptLanguage())
if !hasPermission {
c.ResponseError(err.Error())
return
}
} else {
if code != c.GetSession("verifiedCode") {
c.ResponseError(c.T("general:Missing parameter"))
return
}
if userId != c.GetSession("verifiedUserId") {
c.ResponseError(c.T("general:Wrong userId"))
return
}
c.SetSession("verifiedCode", "")
c.SetSession("verifiedUserId", "")
}
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
}
isAdmin := c.IsAdmin()
if isAdmin {
if oldPassword != "" {
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
}
} else if code == "" {
if user.Ldap == "" {
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
} else {
err = object.CheckLdapUserPassword(targetUser, oldPassword, c.GetAcceptLanguage())
}
if err != nil {
c.ResponseError(err.Error())
return
}
}
msg := object.CheckPasswordComplexity(targetUser, newPassword)
if msg != "" {
c.ResponseError(msg)
return
}
organization, err := object.GetOrganizationByUser(targetUser)
if err != nil {
c.ResponseError(err.Error())
return
}
if organization == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:the organization: %s is not found"), targetUser.Owner))
return
}
application, err := object.GetApplicationByUser(targetUser)
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:the application for user %s is not found"), userId))
return
}
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
err = object.CheckEntryIp(clientIp, targetUser, application, organization, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
targetUser.Password = newPassword
object.SetUserField(targetUser, "password", targetUser.Password)
c.Data["json"] = Response{Status: "ok"}
c.ServeJSON()
targetUser.UpdateUserPassword(organization)
targetUser.NeedUpdatePassword = false
targetUser.LastChangePasswordTime = util.GetCurrentTime()
if user.Ldap == "" {
_, err = object.UpdateUser(userId, targetUser, []string{"password", "password_salt", "need_update_password", "password_type", "last_change_password_time"}, false)
} else {
if isAdmin {
err = object.ResetLdapPassword(targetUser, "", newPassword, c.GetAcceptLanguage())
} else {
err = object.ResetLdapPassword(targetUser, oldPassword, newPassword, c.GetAcceptLanguage())
}
}
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}
// CheckUserPassword
// @Title CheckUserPassword
// @router /check-user-password [post]
// @Tag User API
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) CheckUserPassword() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
@ -327,11 +604,15 @@ func (c *ApiController) CheckUserPassword() {
return
}
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
if msg == "" {
c.ResponseOk()
/*
* Verified password with user as subject, if field ldap not empty,
* then `isPasswordWithLdapEnabled` is true
*/
_, err = object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage(), false, false, user.Ldap != "")
if err != nil {
c.ResponseError(err.Error())
} else {
c.ResponseError(msg)
c.ResponseOk()
}
}
@ -349,8 +630,13 @@ func (c *ApiController) GetSortedUsers() {
sorter := c.Input().Get("sorter")
limit := util.ParseInt(c.Input().Get("limit"))
c.Data["json"] = object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
c.ServeJSON()
users, err := object.GetMaskedUsers(object.GetSortedUsers(owner, sorter, limit))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(users)
}
// GetUserCount
@ -365,13 +651,65 @@ func (c *ApiController) GetUserCount() {
owner := c.Input().Get("owner")
isOnline := c.Input().Get("isOnline")
count := 0
var count int64
var err error
if isOnline == "" {
count = object.GetUserCount(owner, "", "")
count, err = object.GetUserCount(owner, "", "", "")
} else {
count = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
count, err = object.GetOnlineUserCount(owner, util.ParseInt(isOnline))
}
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = count
c.ServeJSON()
c.ResponseOk(count)
}
// AddUserKeys
// @Title AddUserKeys
// @router /add-user-keys [post]
// @Tag User API
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) AddUserKeys() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
c.ResponseError(err.Error())
return
}
isAdmin := c.IsAdmin()
affected, err := object.AddUserKeys(&user, isAdmin)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(affected)
}
func (c *ApiController) RemoveUserFromGroup() {
owner := c.Ctx.Request.Form.Get("owner")
name := c.Ctx.Request.Form.Get("name")
groupName := c.Ctx.Request.Form.Get("groupName")
organization, err := object.GetOrganization(util.GetId("admin", owner))
if err != nil {
return
}
item := object.GetAccountItemByName("Groups", organization)
res, msg := object.CheckAccountItemModifyRule(item, c.IsAdmin(), c.GetAcceptLanguage())
if !res {
c.ResponseError(msg)
return
}
affected, err := object.DeleteGroupForUser(util.GetId(owner, name), util.GetId(owner, groupName))
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(affected)
}

View File

@ -19,13 +19,14 @@ import (
"io"
"mime/multipart"
"os"
"path/filepath"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func saveFile(path string, file *multipart.File) (err error) {
f, err := os.Create(path)
f, err := os.Create(filepath.Clean(path))
if err != nil {
return err
}
@ -47,20 +48,25 @@ 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 := object.UploadUsers(owner, fileId)
affected, err := object.UploadUsers(owner, path)
if err != nil {
c.ResponseError(err.Error())
return
}
if affected {
c.ResponseOk()
} else {
c.ResponseError(c.T("user_upload:Failed to import users"))
c.ResponseError(c.T("general:Failed to import users"))
}
}

View File

@ -1,135 +0,0 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/casdoor/casdoor/object"
)
func checkPermissionForUpdateUser(userId string, newUser object.User, c *ApiController) (bool, string) {
oldUser := object.GetUser(userId)
organization := object.GetOrganizationByUser(oldUser)
var itemsChanged []*object.AccountItem
if oldUser.Owner != newUser.Owner {
item := object.GetAccountItemByName("Organization", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Name != newUser.Name {
item := object.GetAccountItemByName("Name", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Id != newUser.Id {
item := object.GetAccountItemByName("ID", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.DisplayName != newUser.DisplayName {
item := object.GetAccountItemByName("Display name", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Avatar != newUser.Avatar {
item := object.GetAccountItemByName("Avatar", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Type != newUser.Type {
item := object.GetAccountItemByName("User type", organization)
itemsChanged = append(itemsChanged, item)
}
// The password is *** when not modified
if oldUser.Password != newUser.Password && newUser.Password != "***" {
item := object.GetAccountItemByName("Password", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Email != newUser.Email {
item := object.GetAccountItemByName("Email", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Phone != newUser.Phone {
item := object.GetAccountItemByName("Phone", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Region != newUser.Region {
item := object.GetAccountItemByName("Country/Region", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Location != newUser.Location {
item := object.GetAccountItemByName("Location", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Affiliation != newUser.Affiliation {
item := object.GetAccountItemByName("Affiliation", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Title != newUser.Title {
item := object.GetAccountItemByName("Title", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Homepage != newUser.Homepage {
item := object.GetAccountItemByName("Homepage", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Bio != newUser.Bio {
item := object.GetAccountItemByName("Bio", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.Tag != newUser.Tag {
item := object.GetAccountItemByName("Tag", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.SignupApplication != newUser.SignupApplication {
item := object.GetAccountItemByName("Signup application", organization)
itemsChanged = append(itemsChanged, item)
}
oldUserPropertiesJson, _ := json.Marshal(oldUser.Properties)
newUserPropertiesJson, _ := json.Marshal(newUser.Properties)
if string(oldUserPropertiesJson) != string(newUserPropertiesJson) {
item := object.GetAccountItemByName("Properties", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsAdmin != newUser.IsAdmin {
item := object.GetAccountItemByName("Is admin", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsGlobalAdmin != newUser.IsGlobalAdmin {
item := object.GetAccountItemByName("Is global admin", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsForbidden != newUser.IsForbidden {
item := object.GetAccountItemByName("Is forbidden", organization)
itemsChanged = append(itemsChanged, item)
}
if oldUser.IsDeleted != newUser.IsDeleted {
item := object.GetAccountItemByName("Is deleted", organization)
itemsChanged = append(itemsChanged, item)
}
currentUser := c.getCurrentUser()
if currentUser == nil && c.IsGlobalAdmin() {
currentUser = &object.User{
IsGlobalAdmin: true,
}
}
for i := range itemsChanged {
if pass, err := object.CheckAccountItemModifyRule(itemsChanged[i], currentUser, c.GetAcceptLanguage()); !pass {
return pass, err
}
}
return true, ""
}

View File

@ -16,7 +16,6 @@ package controllers
import (
"fmt"
"strconv"
"strings"
"github.com/casdoor/casdoor/conf"
@ -46,6 +45,22 @@ func (c *ApiController) ResponseOk(data ...interface{}) {
// ResponseError ...
func (c *ApiController) ResponseError(error string, data ...interface{}) {
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
if enableErrorMask2 {
error = c.T("subscription:Error")
resp := &Response{Status: "error", Msg: error}
c.ResponseJsonData(resp, data...)
return
}
enableErrorMask := conf.GetConfigBool("enableErrorMask")
if enableErrorMask {
if strings.HasPrefix(error, "The user: ") && strings.HasSuffix(error, " doesn't exist") || strings.HasPrefix(error, "用户: ") && strings.HasSuffix(error, "不存在") {
error = c.T("check:password or code is incorrect")
}
}
resp := &Response{Status: "error", Msg: error}
c.ResponseJsonData(resp, data...)
}
@ -56,11 +71,11 @@ func (c *ApiController) T(error string) string {
// GetAcceptLanguage ...
func (c *ApiController) GetAcceptLanguage() string {
lang := c.Ctx.Request.Header.Get("Accept-Language")
if lang == "" || !strings.Contains(conf.GetConfigString("languages"), lang[0:2]) {
lang = "en"
language := c.Ctx.Request.Header.Get("Accept-Language")
if len(language) > 2 {
language = language[0:2]
}
return lang[0:2]
return conf.GetLanguage(language)
}
// SetTokenErrorHttpStatus ...
@ -97,12 +112,24 @@ func (c *ApiController) RequireSignedInUser() (*object.User, bool) {
return nil, false
}
user := object.GetUser(userId)
if object.IsAppUser(userId) {
tmpUserId := c.Input().Get("userId")
if tmpUserId != "" {
userId = tmpUserId
}
}
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return nil, false
}
if user == nil {
c.ClearUserSession()
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return nil, false
}
return user, true
}
@ -116,46 +143,122 @@ func (c *ApiController) RequireAdmin() (string, bool) {
if user.Owner == "built-in" {
return "", true
}
if !user.IsAdmin {
c.ResponseError(c.T("general:this operation requires administrator to perform"))
return "", false
}
return user.Owner, true
}
func getInitScore(organization *object.Organization) (int, error) {
if organization != nil {
return organization.InitScore, nil
func (c *ApiController) IsOrgAdmin() (bool, bool) {
userId, ok := c.RequireSignedIn()
if !ok {
return false, true
}
if object.IsAppUser(userId) {
return true, true
}
user, err := object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return false, false
}
if user == nil {
c.ClearUserSession()
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), userId))
return false, false
}
return user.IsAdmin, true
}
// IsMaskedEnabled ...
func (c *ApiController) IsMaskedEnabled() (bool, bool) {
isMaskEnabled := true
withSecret := c.Input().Get("withSecret")
if withSecret == "1" {
isMaskEnabled = false
if conf.IsDemoMode() {
c.ResponseError(c.T("general:this operation is not allowed in demo mode"))
return false, isMaskEnabled
}
_, ok := c.RequireAdmin()
if !ok {
return false, isMaskEnabled
}
}
return true, isMaskEnabled
}
func refineFullFilePath(fullFilePath string) (string, string) {
tokens := strings.Split(fullFilePath, "/")
if len(tokens) >= 2 && tokens[0] == "Direct" && tokens[1] != "" {
providerName := tokens[1]
res := strings.Join(tokens[2:], "/")
return providerName, "/" + res
} else {
return strconv.Atoi(conf.GetConfigString("initScore"))
return "", fullFilePath
}
}
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, error) {
providerName := c.Input().Get("provider")
if providerName != "" {
provider := object.GetProvider(util.GetId("admin", providerName))
if provider == nil {
c.ResponseError(c.T("util:The provider: %s is not found"), providerName)
return nil, nil, false
if providerName == "" {
field := c.Input().Get("field")
value := c.Input().Get("value")
if field == "provider" && value != "" {
providerName = value
} else {
fullFilePath := c.Input().Get("fullFilePath")
providerName, _ = refineFullFilePath(fullFilePath)
}
return provider, nil, true
}
if providerName != "" {
provider, err := object.GetProvider(util.GetId("admin", providerName))
if err != nil {
return nil, err
}
if provider == nil {
err = fmt.Errorf(c.T("util:The provider: %s is not found"), providerName)
return nil, err
}
return provider, nil
}
userId, ok := c.RequireSignedIn()
if !ok {
return nil, nil, false
return nil, fmt.Errorf(c.T("general:Please login first"))
}
application, err := object.GetApplicationByUserId(userId)
if err != nil {
return nil, err
}
application, user := object.GetApplicationByUserId(userId)
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("util:No application is found for userId: %s"), userId))
return nil, nil, false
return nil, fmt.Errorf(c.T("util:No application is found for userId: %s"), userId)
}
provider, err := application.GetProviderByCategory(category)
if err != nil {
return nil, err
}
provider := application.GetProviderByCategory(category)
if provider == nil {
c.ResponseError(fmt.Sprintf(c.T("util:No provider for category: %s is found for application: %s"), category, application.Name))
return nil, nil, false
return nil, fmt.Errorf(c.T("util:No provider for category: %s is found for application: %s"), category, application.Name)
}
return provider, user, true
return provider, nil
}
func checkQuotaForApplication(count int) error {
@ -191,13 +294,30 @@ func checkQuotaForProvider(count int) error {
return nil
}
func checkQuotaForUser(count int) error {
func checkQuotaForUser() error {
quota := conf.GetConfigQuota().User
if quota == -1 {
return nil
}
if count >= quota {
count, err := object.GetUserCount("", "", "", "")
if err != nil {
return err
}
if int(count) >= quota {
return fmt.Errorf("user quota is exceeded")
}
return nil
}
func getInvalidSmsReceivers(smsForm SmsForm) []string {
var invalidReceivers []string
for _, receiver := range smsForm.Receivers {
// The receiver phone format: E164 like +8613854673829 +441932567890
if !util.IsPhoneValid(receiver, "") {
invalidReceivers = append(invalidReceivers, receiver)
}
}
return invalidReceivers
}

View File

@ -15,151 +15,354 @@
package controllers
import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/captcha"
"github.com/casdoor/casdoor/form"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
func (c *ApiController) getCurrentUser() *object.User {
var user *object.User
userId := c.GetSessionUsername()
if userId == "" {
user = nil
const (
SignupVerification = "signup"
ResetVerification = "reset"
LoginVerification = "login"
ForgetVerification = "forget"
MfaSetupVerification = "mfaSetup"
MfaAuthVerification = "mfaAuth"
)
// GetVerifications
// @Title GetVerifications
// @Tag Verification API
// @Description get payments
// @Param owner query string true "The owner of payments"
// @Success 200 {array} object.Verification The Response object
// @router /get-payments [get]
func (c *ApiController) GetVerifications() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
payments, err := object.GetVerifications(owner)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payments)
} else {
user = object.GetUser(userId)
limit := util.ParseInt(limit)
count, err := object.GetVerificationCount(owner, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
payments, err := object.GetPaginationVerifications(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payments, paginator.Nums())
}
return user
}
// GetUserVerifications
// @Title GetUserVerifications
// @Tag Verification API
// @Description get payments for a user
// @Param owner query string true "The owner of payments"
// @Param organization query string true "The organization of the user"
// @Param user query string true "The username of the user"
// @Success 200 {array} object.Verification The Response object
// @router /get-user-payments [get]
func (c *ApiController) GetUserVerifications() {
owner := c.Input().Get("owner")
user := c.Input().Get("user")
payments, err := object.GetUserVerifications(owner, user)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payments)
}
// GetVerification
// @Title GetVerification
// @Tag Verification API
// @Description get payment
// @Param id query string true "The id ( owner/name ) of the payment"
// @Success 200 {object} object.Verification The Response object
// @router /get-payment [get]
func (c *ApiController) GetVerification() {
id := c.Input().Get("id")
payment, err := object.GetVerification(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payment)
}
// SendVerificationCode ...
// @Title SendVerificationCode
// @Tag Verification API
// @router /send-verification-code [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) SendVerificationCode() {
destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest")
checkType := c.Ctx.Request.Form.Get("checkType")
checkId := c.Ctx.Request.Form.Get("checkId")
checkKey := c.Ctx.Request.Form.Get("checkKey")
checkUser := c.Ctx.Request.Form.Get("checkUser")
applicationId := c.Ctx.Request.Form.Get("applicationId")
method := c.Ctx.Request.Form.Get("method")
remoteAddr := util.GetIPFromRequest(c.Ctx.Request)
if destType == "" {
c.ResponseError(c.T("general:Missing parameter") + ": type.")
return
}
if dest == "" {
c.ResponseError(c.T("general:Missing parameter") + ": dest.")
return
}
if applicationId == "" {
c.ResponseError(c.T("general:Missing parameter") + ": applicationId.")
return
}
if !strings.Contains(applicationId, "/") {
c.ResponseError(c.T("verification:Wrong parameter") + ": applicationId.")
return
}
if checkType == "" {
c.ResponseError(c.T("general:Missing parameter") + ": checkType.")
var vform form.VerificationForm
err := c.ParseForm(&vform)
if err != nil {
c.ResponseError(err.Error())
return
}
captchaProvider := captcha.GetCaptchaProvider(checkType)
clientIp := util.GetClientIpFromRequest(c.Ctx.Request)
if captchaProvider != nil {
if checkKey == "" {
c.ResponseError(c.T("general:Missing parameter") + ": checkKey.")
if msg := vform.CheckParameter(form.SendVerifyCode, c.GetAcceptLanguage()); msg != "" {
c.ResponseError(msg)
return
}
provider, err := object.GetCaptchaProviderByApplication(vform.ApplicationId, "false", c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
if provider != nil {
if vform.CaptchaType != provider.Type {
c.ResponseError(c.T("verification:Turing test failed."))
return
}
isHuman, err := captchaProvider.VerifyCaptcha(checkKey, checkId)
if provider.Type != "Default" {
vform.ClientSecret = provider.ClientSecret
}
if vform.CaptchaType != "none" {
if captchaProvider := captcha.GetCaptchaProvider(vform.CaptchaType); captchaProvider == nil {
c.ResponseError(c.T("general:don't support captchaProvider: ") + vform.CaptchaType)
return
} else if isHuman, err := captchaProvider.VerifyCaptcha(vform.CaptchaToken, provider.ClientId, vform.ClientSecret, provider.ClientId2); err != nil {
c.ResponseError(err.Error())
return
} else if !isHuman {
c.ResponseError(c.T("verification:Turing test failed."))
return
}
}
}
application, err := object.GetApplication(vform.ApplicationId)
if err != nil {
c.ResponseError(err.Error())
return
}
organization, err := object.GetOrganization(util.GetId(application.Owner, application.Organization))
if err != nil {
c.ResponseError(c.T(err.Error()))
}
if organization == nil {
c.ResponseError(c.T("check:Organization does not exist"))
return
}
var user *object.User
// checkUser != "", means method is ForgetVerification
if vform.CheckUser != "" {
owner := application.Organization
user, err = object.GetUser(util.GetId(owner, vform.CheckUser))
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil || user.IsDeleted {
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
return
}
if !isHuman {
c.ResponseError(c.T("verification:Turing test failed."))
if user.IsForbidden {
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
return
}
}
user := c.getCurrentUser()
application := object.GetApplication(applicationId)
organization := object.GetOrganization(fmt.Sprintf("%s/%s", application.Owner, application.Organization))
if organization == nil {
c.ResponseError(c.T("verification:Organization does not exist"))
return
}
if checkUser == "true" && user == nil && object.GetUserByFields(organization.Name, dest) == nil {
c.ResponseError(c.T("general:Please login first"))
return
// mfaUserSession != "", means method is MfaAuthVerification
if mfaUserSession := c.getMfaUserSession(); mfaUserSession != "" {
user, err = object.GetUser(mfaUserSession)
if err != nil {
c.ResponseError(err.Error())
return
}
}
sendResp := errors.New("invalid dest type")
if user == nil && checkUser != "" && checkUser != "true" {
name := application.Organization
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
}
switch destType {
case "email":
if user != nil && util.GetMaskedEmail(user.Email) == dest {
dest = user.Email
}
if !util.IsEmailValid(dest) {
c.ResponseError(c.T("verification:Email is invalid"))
switch vform.Type {
case object.VerifyTypeEmail:
if !util.IsEmailValid(vform.Dest) {
c.ResponseError(c.T("check:Email is invalid"))
return
}
userByEmail := object.GetUserByEmail(organization.Name, dest)
if userByEmail == nil && method != "signup" && method != "reset" {
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
if user != nil && util.GetMaskedEmail(user.Email) == vform.Dest {
vform.Dest = user.Email
}
user, err = object.GetUserByEmail(organization.Name, vform.Dest)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
return
}
} else if vform.Method == ResetVerification {
user = c.getCurrentUser()
} else if vform.Method == MfaAuthVerification {
mfaProps := user.GetMfaProps(object.EmailType, false)
if user != nil && util.GetMaskedEmail(mfaProps.Secret) == vform.Dest {
vform.Dest = mfaProps.Secret
}
}
provider, err = application.GetEmailProvider(vform.Method)
if err != nil {
c.ResponseError(err.Error())
return
}
if provider == nil {
c.ResponseError(fmt.Sprintf(c.T("verification:please add an Email provider to the \"Providers\" list for the application: %s"), application.Name))
return
}
provider := application.GetEmailProvider()
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone":
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
dest = user.Phone
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, clientIp, vform.Dest)
case object.VerifyTypePhone:
if vform.Method == LoginVerification || vform.Method == ForgetVerification {
if user != nil && util.GetMaskedPhone(user.Phone) == vform.Dest {
vform.Dest = user.Phone
}
if user, err = object.GetUserByPhone(organization.Name, vform.Dest); err != nil {
c.ResponseError(err.Error())
return
} else if user == nil {
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
return
}
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
} else if vform.Method == ResetVerification || vform.Method == MfaSetupVerification {
if vform.CountryCode == "" {
if user = c.getCurrentUser(); user != nil {
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
}
}
} else if vform.Method == MfaAuthVerification {
mfaProps := user.GetMfaProps(object.SmsType, false)
if user != nil && util.GetMaskedPhone(mfaProps.Secret) == vform.Dest {
vform.Dest = mfaProps.Secret
}
vform.CountryCode = mfaProps.CountryCode
vform.CountryCode = user.GetCountryCode(vform.CountryCode)
}
if !util.IsPhoneCnValid(dest) {
c.ResponseError(c.T("verification:Phone number is invalid"))
provider, err = application.GetSmsProvider(vform.Method, vform.CountryCode)
if err != nil {
c.ResponseError(err.Error())
return
}
if provider == nil {
c.ResponseError(fmt.Sprintf(c.T("verification:please add a SMS provider to the \"Providers\" list for the application: %s"), application.Name))
return
}
userByPhone := object.GetUserByPhone(organization.Name, dest)
if userByPhone == nil && method != "signup" && method != "reset" {
c.ResponseError(c.T("verification:the user does not exist, please sign up first"))
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))
return
} else {
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, clientIp, phone)
}
dest = fmt.Sprintf("+%s%s", organization.PhonePrefix, dest)
provider := application.GetSmsProvider()
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
}
if sendResp != nil {
c.Data["json"] = Response{Status: "error", Msg: sendResp.Error()}
c.ResponseError(sendResp.Error())
} else {
c.Data["json"] = Response{Status: "ok"}
c.ResponseOk()
}
}
// VerifyCaptcha ...
// @Title VerifyCaptcha
// @Tag Verification API
// @router /verify-captcha [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) VerifyCaptcha() {
var vform form.VerificationForm
err := c.ParseForm(&vform)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ServeJSON()
if msg := vform.CheckParameter(form.VerifyCaptcha, c.GetAcceptLanguage()); msg != "" {
c.ResponseError(msg)
return
}
captchaProvider, err := object.GetCaptchaProviderByOwnerName(vform.ApplicationId, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
if captchaProvider.Type != "Default" {
vform.ClientSecret = captchaProvider.ClientSecret
}
provider := captcha.GetCaptchaProvider(vform.CaptchaType)
if provider == nil {
c.ResponseError(c.T("verification:Invalid captcha provider."))
return
}
isValid, err := provider.VerifyCaptcha(vform.CaptchaToken, captchaProvider.ClientId, vform.ClientSecret, captchaProvider.ClientId2)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(isValid)
}
// ResetEmailOrPhone ...
// @Tag Account API
// @Title ResetEmailOrPhone
// @router /api/reset-email-or-phone [post]
// @router /reset-email-or-phone [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) ResetEmailOrPhone() {
user, ok := c.RequireSignedInUser()
if !ok {
@ -169,15 +372,21 @@ func (c *ApiController) ResetEmailOrPhone() {
destType := c.Ctx.Request.Form.Get("type")
dest := c.Ctx.Request.Form.Get("dest")
code := c.Ctx.Request.Form.Get("code")
if len(dest) == 0 || len(code) == 0 || len(destType) == 0 {
if util.IsStringsEmpty(destType, dest, code) {
c.ResponseError(c.T("general:Missing parameter"))
return
}
checkDest := dest
organization := object.GetOrganizationByUser(user)
if destType == "phone" {
if object.HasUserByField(user.Owner, "phone", user.Phone) {
organization, err := object.GetOrganizationByUser(user)
if err != nil {
c.ResponseError(c.T(err.Error()))
return
}
if destType == object.VerifyTypePhone {
if object.HasUserByField(user.Owner, "phone", dest) {
c.ResponseError(c.T("check:Phone already exists"))
return
}
@ -188,18 +397,16 @@ func (c *ApiController) ResetEmailOrPhone() {
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user, c.GetAcceptLanguage()); !pass {
if pass, errMsg := object.CheckAccountItemModifyRule(phoneItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
c.ResponseError(errMsg)
return
}
phonePrefix := "86"
if organization != nil && organization.PhonePrefix != "" {
phonePrefix = organization.PhonePrefix
if checkDest, ok = util.GetE164Number(dest, user.GetCountryCode("")); !ok {
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
return
}
checkDest = fmt.Sprintf("+%s%s", phonePrefix, dest)
} else if destType == "email" {
if object.HasUserByField(user.Owner, "email", user.Email) {
} else if destType == object.VerifyTypeEmail {
if object.HasUserByField(user.Owner, "email", dest) {
c.ResponseError(c.T("check:Email already exists"))
return
}
@ -210,62 +417,125 @@ func (c *ApiController) ResetEmailOrPhone() {
return
}
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user, c.GetAcceptLanguage()); !pass {
if pass, errMsg := object.CheckAccountItemModifyRule(emailItem, user.IsAdminUser(), c.GetAcceptLanguage()); !pass {
c.ResponseError(errMsg)
return
}
}
if ret := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage()); len(ret) != 0 {
c.ResponseError(ret)
result, err := object.CheckVerificationCode(checkDest, code, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(c.T(err.Error()))
return
}
if result.Code != object.VerificationSuccess {
c.ResponseError(result.Msg)
return
}
switch destType {
case "email":
case object.VerifyTypeEmail:
user.Email = dest
object.SetUserField(user, "email", user.Email)
case "phone":
user.EmailVerified = true
_, err = object.UpdateUser(user.GetId(), user, []string{"email", "email_verified"}, false)
case object.VerifyTypePhone:
user.Phone = dest
object.SetUserField(user, "phone", user.Phone)
_, err = object.SetUserField(user, "phone", user.Phone)
default:
c.ResponseError(c.T("verification:Unknown type"))
return
}
object.DisableVerificationCode(checkDest)
c.Data["json"] = Response{Status: "ok"}
c.ServeJSON()
}
// VerifyCaptcha ...
// @Title VerifyCaptcha
// @Tag Verification API
// @router /verify-captcha [post]
func (c *ApiController) VerifyCaptcha() {
captchaType := c.Ctx.Request.Form.Get("captchaType")
captchaToken := c.Ctx.Request.Form.Get("captchaToken")
clientSecret := c.Ctx.Request.Form.Get("clientSecret")
if captchaToken == "" {
c.ResponseError(c.T("general:Missing parameter") + ": captchaToken.")
return
}
if clientSecret == "" {
c.ResponseError(c.T("general:Missing parameter") + ": clientSecret.")
return
}
provider := captcha.GetCaptchaProvider(captchaType)
if provider == nil {
c.ResponseError(c.T("verification:Invalid captcha provider."))
return
}
isValid, err := provider.VerifyCaptcha(captchaToken, clientSecret)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(isValid)
err = object.DisableVerificationCode(checkDest)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}
// VerifyCode
// @Tag Verification API
// @Title VerifyCode
// @router /verify-code [post]
// @Success 200 {object} object.Userinfo The Response object
func (c *ApiController) VerifyCode() {
var authForm form.AuthForm
err := json.Unmarshal(c.Ctx.Input.RequestBody, &authForm)
if err != nil {
c.ResponseError(err.Error())
return
}
var user *object.User
if authForm.Name != "" {
user, err = object.GetUserByFields(authForm.Organization, authForm.Name)
if err != nil {
c.ResponseError(err.Error())
return
}
}
var checkDest string
if strings.Contains(authForm.Username, "@") {
if user != nil && util.GetMaskedEmail(user.Email) == authForm.Username {
authForm.Username = user.Email
}
checkDest = authForm.Username
} else {
if user != nil && util.GetMaskedPhone(user.Phone) == authForm.Username {
authForm.Username = user.Phone
}
}
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
c.ResponseError(err.Error())
return
} else if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(authForm.Organization, authForm.Username)))
return
}
verificationCodeType := object.GetVerifyType(authForm.Username)
if verificationCodeType == object.VerifyTypePhone {
authForm.CountryCode = user.GetCountryCode(authForm.CountryCode)
var ok bool
if checkDest, ok = util.GetE164Number(authForm.Username, authForm.CountryCode); !ok {
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), authForm.CountryCode))
return
}
}
passed, err := c.checkOrgMasterVerificationCode(user, authForm.Code)
if err != nil {
c.ResponseError(c.T(err.Error()))
return
}
if !passed {
result, err := object.CheckVerificationCode(checkDest, authForm.Code, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
if result.Code != object.VerificationSuccess {
c.ResponseError(result.Msg)
return
}
err = object.DisableVerificationCode(checkDest)
if err != nil {
c.ResponseError(err.Error())
return
}
}
c.SetSession("verifiedCode", authForm.Code)
c.SetSession("verifiedUserId", user.GetId())
c.ResponseOk()
}

View File

@ -0,0 +1,36 @@
// Copyright 2025 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 (
"fmt"
"github.com/casdoor/casdoor/object"
)
func (c *ApiController) checkOrgMasterVerificationCode(user *object.User, code string) (bool, error) {
organization, err := object.GetOrganizationByUser(user)
if err != nil {
return false, err
}
if organization == nil {
return false, fmt.Errorf("The organization: %s does not exist", user.Owner)
}
if organization.MasterVerificationCode != "" && organization.MasterVerificationCode == code {
return true, nil
}
return false, nil
}

View File

@ -16,13 +16,14 @@ package controllers
import (
"bytes"
"fmt"
"encoding/base64"
"io"
"github.com/casdoor/casdoor/form"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
)
// WebAuthnSignupBegin
@ -32,7 +33,12 @@ import (
// @Success 200 {object} protocol.CredentialCreation The CredentialCreationOptions object
// @router /webauthn/signup/begin [get]
func (c *ApiController) WebAuthnSignupBegin() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
}
user := c.getCurrentUser()
if user == nil {
c.ResponseError(c.T("general:Please login first"))
@ -60,10 +66,15 @@ func (c *ApiController) WebAuthnSignupBegin() {
// @Tag User API
// @Description WebAuthn Registration Flow 2nd stage
// @Param body body protocol.CredentialCreationResponse true "authenticator attestation Response"
// @Success 200 {object} Response "The Response object"
// @Success 200 {object} controllers.Response "The Response object"
// @router /webauthn/signup/finish [post]
func (c *ApiController) WebAuthnSignupFinish() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
}
user := c.getCurrentUser()
if user == nil {
c.ResponseError(c.T("general:Please login first"))
@ -83,7 +94,12 @@ func (c *ApiController) WebAuthnSignupFinish() {
return
}
isGlobalAdmin := c.IsGlobalAdmin()
user.AddCredentials(*credential, isGlobalAdmin)
_, err = user.AddCredentials(*credential, isGlobalAdmin)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk()
}
@ -96,20 +112,13 @@ func (c *ApiController) WebAuthnSignupFinish() {
// @Success 200 {object} protocol.CredentialAssertion The CredentialAssertion object
// @router /webauthn/signin/begin [get]
func (c *ApiController) WebAuthnSigninBegin() {
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
userOwner := c.Input().Get("owner")
userName := c.Input().Get("name")
user := object.GetUserByFields(userOwner, userName)
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), util.GetId(userOwner, userName)))
return
}
if len(user.WebauthnCredentials) == 0 {
c.ResponseError(c.T("webauthn:Found no credentials for this user"))
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
}
options, sessionData, err := webauthnObj.BeginLogin(user)
options, sessionData, err := webauthnObj.BeginDiscoverableLogin()
if err != nil {
c.ResponseError(err.Error())
return
@ -120,15 +129,21 @@ func (c *ApiController) WebAuthnSigninBegin() {
}
// WebAuthnSigninFinish
// @Title WebAuthnSigninBegin
// @Title WebAuthnSigninFinish
// @Tag Login API
// @Description WebAuthn Login Flow 2nd stage
// @Param body body protocol.CredentialAssertionResponse true "authenticator assertion Response"
// @Success 200 {object} Response "The Response object"
// @Success 200 {object} controllers.Response "The Response object"
// @router /webauthn/signin/finish [post]
func (c *ApiController) WebAuthnSigninFinish() {
responseType := c.Input().Get("responseType")
webauthnObj := object.GetWebAuthnObject(c.Ctx.Request.Host)
clientId := c.Input().Get("clientId")
webauthnObj, err := object.GetWebAuthnObject(c.Ctx.Request.Host)
if err != nil {
c.ResponseError(err.Error())
return
}
sessionObj := c.GetSession("authentication")
sessionData, ok := sessionObj.(webauthn.SessionData)
if !ok {
@ -136,20 +151,39 @@ func (c *ApiController) WebAuthnSigninFinish() {
return
}
c.Ctx.Request.Body = io.NopCloser(bytes.NewBuffer(c.Ctx.Input.RequestBody))
userId := string(sessionData.UserID)
user := object.GetUser(userId)
_, err := webauthnObj.FinishLogin(user, sessionData, c.Ctx.Request)
var user *object.User
handler := func(rawID, userHandle []byte) (webauthn.User, error) {
user, err = object.GetUserByWebauthID(base64.StdEncoding.EncodeToString(rawID))
if err != nil {
return nil, err
}
return user, nil
}
_, err = webauthnObj.FinishDiscoverableLogin(handler, sessionData, c.Ctx.Request)
if err != nil {
c.ResponseError(err.Error())
return
}
c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId)
c.SetSessionUsername(user.GetId())
util.LogInfo(c.Ctx, "API: [%s] signed in", user.GetId())
application := object.GetApplicationByUser(user)
var form RequestForm
form.Type = responseType
resp := c.HandleLoggedIn(application, user, &form)
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
}
var authForm form.AuthForm
authForm.Type = responseType
resp := c.HandleLoggedIn(application, user, &authForm)
c.Data["json"] = resp
c.ServeJSON()
}

View File

@ -26,9 +26,10 @@ import (
// @Title GetWebhooks
// @Tag Webhook API
// @Description get webhooks
// @Param owner query string true "The owner of webhooks"
// @Param owner query string built-in/admin true "The owner of webhooks"
// @Success 200 {array} object.Webhook The Response object
// @router /get-webhooks [get]
// @Security test_apiKey
func (c *ApiController) GetWebhooks() {
owner := c.Input().Get("owner")
limit := c.Input().Get("pageSize")
@ -37,13 +38,32 @@ func (c *ApiController) GetWebhooks() {
value := c.Input().Get("value")
sortField := c.Input().Get("sortField")
sortOrder := c.Input().Get("sortOrder")
organization := c.Input().Get("organization")
if limit == "" || page == "" {
c.Data["json"] = object.GetWebhooks(owner)
c.ServeJSON()
webhooks, err := object.GetWebhooks(owner, organization)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(webhooks)
} else {
limit := util.ParseInt(limit)
paginator := pagination.SetPaginator(c.Ctx, limit, int64(object.GetWebhookCount(owner, field, value)))
webhooks := object.GetPaginationWebhooks(owner, paginator.Offset(), limit, field, value, sortField, sortOrder)
count, err := object.GetWebhookCount(owner, organization, field, value)
if err != nil {
c.ResponseError(err.Error())
return
}
paginator := pagination.SetPaginator(c.Ctx, limit, count)
webhooks, err := object.GetPaginationWebhooks(owner, organization, paginator.Offset(), limit, field, value, sortField, sortOrder)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(webhooks, paginator.Nums())
}
}
@ -52,21 +72,26 @@ func (c *ApiController) GetWebhooks() {
// @Title GetWebhook
// @Tag Webhook API
// @Description get webhook
// @Param id query string true "The id of the webhook"
// @Param id query string built-in/admin true "The id ( owner/name ) of the webhook"
// @Success 200 {object} object.Webhook The Response object
// @router /get-webhook [get]
func (c *ApiController) GetWebhook() {
id := c.Input().Get("id")
c.Data["json"] = object.GetWebhook(id)
c.ServeJSON()
webhook, err := object.GetWebhook(id)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(webhook)
}
// UpdateWebhook
// @Title UpdateWebhook
// @Tag Webhook API
// @Description update webhook
// @Param id query string true "The id of the webhook"
// @Param id query string built-in/admin true "The id ( owner/name ) of the webhook"
// @Param body body object.Webhook true "The details of the webhook"
// @Success 200 {object} controllers.Response The Response object
// @router /update-webhook [post]

View File

@ -23,7 +23,7 @@ func NewArgon2idCredManager() *Argon2idCredManager {
return cm
}
func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
func (cm *Argon2idCredManager) GetHashedPassword(password string, salt string) string {
hash, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil {
return ""
@ -31,7 +31,7 @@ func (cm *Argon2idCredManager) GetHashedPassword(password string, userSalt strin
return hash
}
func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
func (cm *Argon2idCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
match, _ := argon2id.ComparePasswordAndHash(plainPwd, hashedPwd)
return match
}

View File

@ -9,7 +9,7 @@ func NewBcryptCredManager() *BcryptCredManager {
return cm
}
func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
func (cm *BcryptCredManager) GetHashedPassword(password string, salt string) string {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return ""
@ -17,7 +17,7 @@ func (cm *BcryptCredManager) GetHashedPassword(password string, userSalt string,
return string(bytes)
}
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
func (cm *BcryptCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPwd), []byte(plainPwd))
return err == nil
}

View File

@ -15,8 +15,8 @@
package cred
type CredManager interface {
GetHashedPassword(password string, userSalt string, organizationSalt string) string
IsPasswordCorrect(password string, passwordHash string, userSalt string, organizationSalt string) bool
GetHashedPassword(password string, salt string) string
IsPasswordCorrect(password string, passwordHash string, salt string) bool
}
func GetCredManager(passwordType string) CredManager {
@ -24,6 +24,8 @@ func GetCredManager(passwordType string) CredManager {
return NewPlainCredManager()
} else if passwordType == "salt" {
return NewSha256SaltCredManager()
} else if passwordType == "sha512-salt" {
return NewSha512SaltCredManager()
} else if passwordType == "md5-salt" {
return NewMd5UserSaltCredManager()
} else if passwordType == "bcrypt" {
@ -32,6 +34,8 @@ func GetCredManager(passwordType string) CredManager {
return NewPbkdf2SaltCredManager()
} else if passwordType == "argon2id" {
return NewArgon2idCredManager()
} else if passwordType == "pbkdf2-django" {
return NewPbkdf2DjangoCredManager()
}
return nil
}

View File

@ -37,14 +37,10 @@ func NewMd5UserSaltCredManager() *Md5UserSaltCredManager {
return cm
}
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
res := getMd5HexDigest(password)
if userSalt != "" {
res = getMd5HexDigest(res + userSalt)
}
return res
func (cm *Md5UserSaltCredManager) GetHashedPassword(password string, salt string) string {
return getMd5HexDigest(getMd5HexDigest(password) + salt)
}
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
func (cm *Md5UserSaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
}

View File

@ -28,13 +28,13 @@ func NewPbkdf2SaltCredManager() *Pbkdf2SaltCredManager {
return cm
}
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
func (cm *Pbkdf2SaltCredManager) GetHashedPassword(password string, salt string) string {
// https://www.keycloak.org/docs/latest/server_admin/index.html#password-database-compromised
decodedSalt, _ := base64.StdEncoding.DecodeString(userSalt)
decodedSalt, _ := base64.StdEncoding.DecodeString(salt)
res := pbkdf2.Key([]byte(password), decodedSalt, 27500, 64, sha256.New)
return base64.StdEncoding.EncodeToString(res)
}
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
func (cm *Pbkdf2SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
}

67
cred/pbkdf2_django.go Normal file
View File

@ -0,0 +1,67 @@
// Copyright 2025 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 cred
import (
"crypto/sha256"
"encoding/base64"
"strconv"
"strings"
"golang.org/x/crypto/pbkdf2"
)
// password type: pbkdf2-django
type Pbkdf2DjangoCredManager struct{}
func NewPbkdf2DjangoCredManager() *Pbkdf2DjangoCredManager {
cm := &Pbkdf2DjangoCredManager{}
return cm
}
func (m *Pbkdf2DjangoCredManager) GetHashedPassword(password string, salt string) string {
iterations := 260000
saltBytes := []byte(salt)
passwordBytes := []byte(password)
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iterations, sha256.Size, sha256.New)
hashBase64 := base64.StdEncoding.EncodeToString(computedHash)
return "pbkdf2_sha256$" + strconv.Itoa(iterations) + "$" + salt + "$" + hashBase64
}
func (m *Pbkdf2DjangoCredManager) IsPasswordCorrect(password string, passwordHash string, _salt string) bool {
parts := strings.Split(passwordHash, "$")
if len(parts) != 4 {
return false
}
algorithm, iterations, salt, hash := parts[0], parts[1], parts[2], parts[3]
if algorithm != "pbkdf2_sha256" {
return false
}
iter, err := strconv.Atoi(iterations)
if err != nil {
return false
}
saltBytes := []byte(salt)
passwordBytes := []byte(password)
computedHash := pbkdf2.Key(passwordBytes, saltBytes, iter, sha256.Size, sha256.New)
computedHashBase64 := base64.StdEncoding.EncodeToString(computedHash)
return computedHashBase64 == hash
}

View File

@ -21,10 +21,10 @@ func NewPlainCredManager() *PlainCredManager {
return cm
}
func (cm *PlainCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
func (cm *PlainCredManager) GetHashedPassword(password string, salt string) string {
return password
}
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
func (cm *PlainCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == plainPwd
}

View File

@ -37,14 +37,10 @@ func NewSha256SaltCredManager() *Sha256SaltCredManager {
return cm
}
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, userSalt string, organizationSalt string) string {
res := getSha256HexDigest(password)
if organizationSalt != "" {
res = getSha256HexDigest(res + organizationSalt)
}
return res
func (cm *Sha256SaltCredManager) GetHashedPassword(password string, salt string) string {
return getSha256HexDigest(getSha256HexDigest(password) + salt)
}
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, userSalt string, organizationSalt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, userSalt, organizationSalt)
func (cm *Sha256SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
}

View File

@ -23,12 +23,12 @@ func TestGetSaltedPassword(t *testing.T) {
password := "123456"
salt := "123"
cm := NewSha256SaltCredManager()
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, "", salt))
fmt.Printf("%s -> %s\n", password, cm.GetHashedPassword(password, salt))
}
func TestGetPassword(t *testing.T) {
password := "123456"
cm := NewSha256SaltCredManager()
// https://passwordsgenerator.net/sha256-hash-generator/
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, "", ""))
fmt.Printf("%s -> %s\n", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92", cm.GetHashedPassword(password, ""))
}

46
cred/sha512-salt.go Normal file
View File

@ -0,0 +1,46 @@
// Copyright 2024 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 cred
import (
"crypto/sha512"
"encoding/hex"
)
type Sha512SaltCredManager struct{}
func getSha512(data []byte) []byte {
hash := sha512.Sum512(data)
return hash[:]
}
func getSha512HexDigest(s string) string {
b := getSha512([]byte(s))
res := hex.EncodeToString(b)
return res
}
func NewSha512SaltCredManager() *Sha512SaltCredManager {
cm := &Sha512SaltCredManager{}
return cm
}
func (cm *Sha512SaltCredManager) GetHashedPassword(password string, salt string) string {
return getSha512HexDigest(getSha512HexDigest(password) + salt)
}
func (cm *Sha512SaltCredManager) IsPasswordCorrect(plainPwd string, hashedPwd string, salt string) bool {
return hashedPwd == cm.GetHashedPassword(plainPwd, salt)
}

View File

@ -17,6 +17,7 @@ package deployment
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/casdoor/casdoor/object"
@ -26,7 +27,21 @@ import (
)
func deployStaticFiles(provider *object.Provider) {
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
certificate := ""
if provider.Category == "Storage" && provider.Type == "Casdoor" {
cert, err := object.GetCert(util.GetId(provider.Owner, provider.Cert))
if err != nil {
panic(err)
}
if cert == nil {
panic(err)
}
certificate = cert.Certificate
}
storageProvider, err := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint, certificate, provider.Content)
if err != nil {
panic(err)
}
if storageProvider == nil {
panic(fmt.Sprintf("the provider type: %s is not supported", provider.Type))
}
@ -45,7 +60,7 @@ func uploadFolder(storageProvider oss.StorageInterface, folder string) {
continue
}
file, err := os.Open(path + filename)
file, err := os.Open(filepath.Clean(path + filename))
if err != nil {
panic(err)
}

View File

@ -25,6 +25,12 @@ import (
)
func TestDeployStaticFiles(t *testing.T) {
provider := object.GetProvider(util.GetId("admin", "provider_storage_aliyun_oss"))
object.InitConfig()
provider, err := object.GetProvider(util.GetId("admin", "provider_storage_aliyun_oss"))
if err != nil {
panic(err)
}
deployStaticFiles(provider)
}

221
email/azure_acs.go Normal file
View File

@ -0,0 +1,221 @@
// 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,
Attachments: []Attachment{},
}
}
func (a *AzureACSEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
email := newEmail(fromAddress, toAddress, subject, content)
postBody, err := json.Marshal(email)
if err != nil {
return err
}
endpoint := strings.TrimSuffix(a.Endpoint, "/")
url := fmt.Sprintf("%s/emails:send?api-version=2023-03-31", endpoint)
bodyBuffer := bytes.NewBuffer(postBody)
req, err := http.NewRequest("POST", url, bodyBuffer)
if err != nil {
return err
}
err = signRequestHMAC(a.AccessKey, req)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("repeatability-request-id", uuid.New().String())
req.Header.Set("repeatability-first-sent", time.Now().UTC().Format(http.TimeFormat))
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
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("status code: %d, error message: %s", resp.StatusCode, commError.Error.Message)
}
if resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("status code: %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))
}

132
email/custom_http.go Normal file
View File

@ -0,0 +1,132 @@
// 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"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"github.com/casdoor/casdoor/proxy"
)
type HttpEmailProvider struct {
endpoint string
method string
httpHeaders map[string]string
bodyMapping map[string]string
contentType string
}
func NewHttpEmailProvider(endpoint string, method string, httpHeaders map[string]string, bodyMapping map[string]string, contentType string) *HttpEmailProvider {
if contentType == "" {
contentType = "application/x-www-form-urlencoded"
}
client := &HttpEmailProvider{
endpoint: endpoint,
method: method,
httpHeaders: httpHeaders,
bodyMapping: bodyMapping,
contentType: contentType,
}
return client
}
func (c *HttpEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
var req *http.Request
var err error
fromNameField := "fromName"
toAddressField := "toAddress"
subjectField := "subject"
contentField := "content"
for k, v := range c.bodyMapping {
switch k {
case "fromName":
fromNameField = v
case "toAddress":
toAddressField = v
case "subject":
subjectField = v
case "content":
contentField = v
}
}
if c.method == "POST" || c.method == "PUT" || c.method == "DELETE" {
bodyMap := make(map[string]string)
bodyMap[fromNameField] = fromName
bodyMap[toAddressField] = toAddress
bodyMap[subjectField] = subject
bodyMap[contentField] = content
var fromValueBytes []byte
if c.contentType == "application/json" {
fromValueBytes, err = json.Marshal(bodyMap)
if err != nil {
return err
}
req, err = http.NewRequest(c.method, c.endpoint, bytes.NewBuffer(fromValueBytes))
} else {
formValues := url.Values{}
for k, v := range bodyMap {
formValues.Add(k, v)
}
req, err = http.NewRequest(c.method, c.endpoint, strings.NewReader(formValues.Encode()))
}
if err != nil {
return err
}
req.Header.Set("Content-Type", c.contentType)
} else if c.method == "GET" {
req, err = http.NewRequest(c.method, c.endpoint, nil)
if err != nil {
return err
}
q := req.URL.Query()
q.Add(fromNameField, fromName)
q.Add(toAddressField, toAddress)
q.Add(subjectField, subject)
q.Add(contentField, content)
req.URL.RawQuery = q.Encode()
} else {
return fmt.Errorf("HttpEmailProvider's Send() error, unsupported method: %s", c.method)
}
for k, v := range c.httpHeaders {
req.Header.Set(k, v)
}
httpClient := proxy.DefaultHttpClient
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HttpEmailProvider's Send() error, custom HTTP Email request failed with status: %s", resp.Status)
}
return err
}

31
email/provider.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 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, endpoint string, method string, httpHeaders map[string]string, bodyMapping map[string]string, contentType string) EmailProvider {
if typ == "Azure ACS" {
return NewAzureACSEmailProvider(clientSecret, host)
} else if typ == "Custom HTTP Email" {
return NewHttpEmailProvider(endpoint, method, httpHeaders, bodyMapping, contentType)
} else if typ == "SendGrid" {
return NewSendgridEmailProvider(clientSecret, host, endpoint)
} else {
return NewSmtpEmailProvider(clientId, clientSecret, host, port, typ, disableSsl)
}
}

87
email/sendgrid.go Normal file
View File

@ -0,0 +1,87 @@
// Copyright 2024 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 (
"encoding/json"
"fmt"
"net/http"
"github.com/sendgrid/sendgrid-go"
"github.com/sendgrid/sendgrid-go/helpers/mail"
)
type SendgridEmailProvider struct {
ApiKey string
Host string
Endpoint string
}
type SendgridResponseBody struct {
Errors []struct {
Message string `json:"message"`
Field interface{} `json:"field"`
Help interface{} `json:"help"`
} `json:"errors"`
}
func NewSendgridEmailProvider(apiKey string, host string, endpoint string) *SendgridEmailProvider {
return &SendgridEmailProvider{ApiKey: apiKey, Host: host, Endpoint: endpoint}
}
func (s *SendgridEmailProvider) Send(fromAddress string, fromName string, toAddress string, subject string, content string) error {
client := s.initSendgridClient()
from := mail.NewEmail(fromName, fromAddress)
to := mail.NewEmail("", toAddress)
message := mail.NewSingleEmail(from, subject, to, "", content)
resp, err := client.Send(message)
if err != nil {
return err
}
if resp.StatusCode >= 300 {
var responseBody SendgridResponseBody
err = json.Unmarshal([]byte(resp.Body), &responseBody)
if err != nil {
return err
}
messages := []string{}
for _, sendgridError := range responseBody.Errors {
messages = append(messages, sendgridError.Message)
}
return fmt.Errorf("status code: %d, error message: %s", resp.StatusCode, messages)
}
if resp.StatusCode != http.StatusAccepted {
return fmt.Errorf("status code: %d", resp.StatusCode)
}
return nil
}
func (s *SendgridEmailProvider) initSendgridClient() *sendgrid.Client {
if s.Host == "" || s.Endpoint == "" {
return sendgrid.NewSendClient(s.ApiKey)
}
request := sendgrid.GetRequest(s.ApiKey, s.Endpoint, s.Host)
request.Method = "POST"
return &sendgrid.Client{Request: request}
}

57
email/smtp.go Normal file
View File

@ -0,0 +1,57 @@
// 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"
"strings"
"github.com/casdoor/casdoor/conf"
"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.NewDialer(host, port, userName, password)
if typ == "SUBMAIL" {
dialer.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
dialer.SSL = !disableSsl
if strings.HasSuffix(host, ".amazonaws.com") {
socks5Proxy := conf.GetConfigString("socks5Proxy")
if socks5Proxy != "" {
dialer.SetSocks5Proxy(socks5Proxy)
}
}
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)
}

81
faceId/aliyun.go Normal file
View File

@ -0,0 +1,81 @@
// Copyright 2025 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 faceId
import (
"strings"
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
facebody20191230 "github.com/alibabacloud-go/facebody-20191230/v5/client"
util "github.com/alibabacloud-go/tea-utils/v2/service"
"github.com/alibabacloud-go/tea/tea"
)
type AliyunFaceIdProvider struct {
AccessKey string
AccessSecret string
Endpoint string
QualityScoreThreshold float32
}
func NewAliyunFaceIdProvider(accessKey string, accessSecret string, endPoint string) *AliyunFaceIdProvider {
return &AliyunFaceIdProvider{
AccessKey: accessKey,
AccessSecret: accessSecret,
Endpoint: endPoint,
QualityScoreThreshold: 0.65,
}
}
func (provider *AliyunFaceIdProvider) Check(base64ImageA string, base64ImageB string) (bool, error) {
config := openapi.Config{
AccessKeyId: tea.String(provider.AccessKey),
AccessKeySecret: tea.String(provider.AccessSecret),
}
config.Endpoint = tea.String(provider.Endpoint)
client, err := facebody20191230.NewClient(&config)
if err != nil {
return false, err
}
compareFaceRequest := &facebody20191230.CompareFaceRequest{
QualityScoreThreshold: tea.Float32(provider.QualityScoreThreshold),
ImageDataA: tea.String(strings.Replace(base64ImageA, "data:image/png;base64,", "", -1)),
ImageDataB: tea.String(strings.Replace(base64ImageB, "data:image/png;base64,", "", -1)),
}
runtime := &util.RuntimeOptions{}
defer func() {
if r := tea.Recover(recover()); r != nil {
err = r
}
}()
result, err := client.CompareFaceWithOptions(compareFaceRequest, runtime)
if err != nil {
return false, err
}
if result == nil {
return false, nil
}
if *result.Body.Data.Thresholds[0] < *result.Body.Data.Confidence {
return true, nil
}
return false, nil
}

23
faceId/provider.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright 2025 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 faceId
type FaceIdProvider interface {
Check(base64ImageA string, base64ImageB string) (bool, error)
}
func GetFaceIdProvider(typ string, clientId string, clientSecret string, endPoint string) FaceIdProvider {
return NewAliyunFaceIdProvider(clientId, clientSecret, endPoint)
}

84
form/auth.go Normal file
View File

@ -0,0 +1,84 @@
// 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 form
import "reflect"
type AuthForm struct {
Type string `json:"type"`
SigninMethod string `json:"signinMethod"`
Organization string `json:"organization"`
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Gender string `json:"gender"`
Bio string `json:"bio"`
Tag string `json:"tag"`
Education string `json:"education"`
Email string `json:"email"`
Phone string `json:"phone"`
Affiliation string `json:"affiliation"`
IdCard string `json:"idCard"`
Language string `json:"language"`
Region string `json:"region"`
InvitationCode string `json:"invitationCode"`
Application string `json:"application"`
ClientId string `json:"clientId"`
Provider string `json:"provider"`
ProviderBack string `json:"providerBack"`
Code string `json:"code"`
State string `json:"state"`
RedirectUri string `json:"redirectUri"`
Method string `json:"method"`
EmailCode string `json:"emailCode"`
PhoneCode string `json:"phoneCode"`
CountryCode string `json:"countryCode"`
AutoSignin bool `json:"autoSignin"`
RelayState string `json:"relayState"`
SamlRequest string `json:"samlRequest"`
SamlResponse string `json:"samlResponse"`
CaptchaType string `json:"captchaType"`
CaptchaToken string `json:"captchaToken"`
ClientSecret string `json:"clientSecret"`
MfaType string `json:"mfaType"`
Passcode string `json:"passcode"`
RecoveryCode string `json:"recoveryCode"`
Plan string `json:"plan"`
Pricing string `json:"pricing"`
FaceId []float64 `json:"faceId"`
FaceIdImage []string `json:"faceIdImage"`
UserCode string `json:"userCode"`
}
func GetAuthFormFieldValue(form *AuthForm, fieldName string) (bool, string) {
val := reflect.ValueOf(*form)
fieldValue := val.FieldByName(fieldName)
if fieldValue.IsValid() && fieldValue.Kind() == reflect.String {
return true, fieldValue.String()
}
return false, ""
}

67
form/verification.go Normal file
View File

@ -0,0 +1,67 @@
// 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 form
import (
"strings"
"github.com/casdoor/casdoor/i18n"
)
type VerificationForm struct {
Dest string `form:"dest"`
Type string `form:"type"`
CountryCode string `form:"countryCode"`
ApplicationId string `form:"applicationId"`
Method string `form:"method"`
CheckUser string `form:"checkUser"`
CaptchaType string `form:"captchaType"`
ClientSecret string `form:"clientSecret"`
CaptchaToken string `form:"captchaToken"`
}
const (
SendVerifyCode = 0
VerifyCaptcha = 1
)
func (form *VerificationForm) CheckParameter(checkType int, lang string) string {
if checkType == SendVerifyCode {
if form.Type == "" {
return i18n.Translate(lang, "general:Missing parameter") + ": type."
}
if form.Dest == "" {
return i18n.Translate(lang, "general:Missing parameter") + ": dest."
}
if form.CaptchaType == "" {
return i18n.Translate(lang, "general:Missing parameter") + ": checkType."
}
if !strings.Contains(form.ApplicationId, "/") {
return i18n.Translate(lang, "verification:Wrong parameter") + ": applicationId."
}
}
if form.CaptchaType != "none" {
if form.CaptchaToken == "" {
return i18n.Translate(lang, "general:Missing parameter") + ": captchaToken."
}
if form.ClientSecret == "" {
return i18n.Translate(lang, "general:Missing parameter") + ": clientSecret."
}
}
return ""
}

271
go.mod
View File

@ -1,58 +1,249 @@
module github.com/casdoor/casdoor
go 1.16
go 1.21
require (
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
github.com/Masterminds/squirrel v1.5.3
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
github.com/aws/aws-sdk-go v1.44.4
github.com/beego/beego v1.12.11
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.4
github.com/alibabacloud-go/facebody-20191230/v5 v5.1.2
github.com/alibabacloud-go/openapi-util v0.1.0
github.com/alibabacloud-go/tea v1.3.2
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
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/v2 v2.30.1
github.com/casbin/xorm-adapter/v3 v3.0.1
github.com/casdoor/go-sms-sender v0.5.1
github.com/casdoor/oss v1.2.0
github.com/casbin/casbin/v2 v2.77.2
github.com/casdoor/go-sms-sender v0.25.0
github.com/casdoor/gomail/v2 v2.1.0
github.com/casdoor/ldapserver v1.2.0
github.com/casdoor/notify v1.0.1
github.com/casdoor/oss v1.8.0
github.com/casdoor/xorm-adapter/v3 v3.1.0
github.com/casvisor/casvisor-go-sdk v1.4.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.9.0
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
github.com/forestmgy/ldapserver v1.1.0
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0
github.com/elimity-com/scim v0.0.0-20230426070224-941a5eac92f3
github.com/fogleman/gg v1.3.0
github.com/go-asn1-ber/asn1-ber v1.5.5
github.com/go-git/go-git/v5 v5.13.0
github.com/go-ldap/ldap/v3 v3.4.6
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.5.0
github.com/golang-jwt/jwt/v4 v4.2.0
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/uuid v1.2.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v1.2.21
github.com/lib/pq v1.8.0
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.10.2
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0
github.com/json-iterator/go v1.1.12
github.com/lestrrat-go/jwx v1.2.29
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/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/markbates/goth v1.79.0
github.com/mitchellh/mapstructure v1.5.0
github.com/nyaruka/phonenumbers v1.1.5
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.11.1
github.com/prometheus/client_model v0.4.0
github.com/qiangmzsx/string-adapter/v2 v2.1.0
github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.6.0
github.com/russellhaering/goxmldsig v1.1.1
github.com/satori/go.uuid v1.2.0
github.com/russellhaering/gosaml2 v0.9.0
github.com/russellhaering/goxmldsig v1.2.0
github.com/sendgrid/sendgrid-go v3.14.0+incompatible
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.0
github.com/stretchr/testify v1.10.0
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/tklauser/go-sysconf v0.3.10 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
gopkg.in/ini.v1 v1.67.0 // 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
golang.org/x/crypto v0.32.0
golang.org/x/net v0.34.0
golang.org/x/oauth2 v0.17.0
golang.org/x/text v0.21.0
google.golang.org/api v0.150.0
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.3.0 // indirect
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84
xorm.io/builder v0.3.12 // indirect
xorm.io/core v0.7.2
xorm.io/xorm v1.1.2
layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68
maunium.net/go/mautrix v0.16.0
modernc.org/sqlite v1.18.2
)
require (
cloud.google.com/go v0.110.8 // indirect
cloud.google.com/go/compute v1.23.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.3 // indirect
cloud.google.com/go/storage v1.35.1 // indirect
dario.cat/mergo v1.0.0 // indirect
github.com/Azure/azure-pipeline-go v0.2.3 // indirect
github.com/Azure/azure-storage-blob-go v0.15.0 // indirect
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect
github.com/RocketChat/Rocket.Chat.Go.SDK v0.0.0-20221121042443-a3fd332d56d9 // indirect
github.com/SherClockHolmes/webpush-go v1.2.0 // indirect
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
github.com/alibabacloud-go/darabonba-number v1.0.4 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
github.com/alibabacloud-go/openplatform-20191219/v2 v2.0.1 // indirect
github.com/alibabacloud-go/tea-fileform v1.1.1 // indirect
github.com/alibabacloud-go/tea-oss-sdk v1.1.3 // indirect
github.com/alibabacloud-go/tea-oss-utils v1.1.0 // indirect
github.com/alibabacloud-go/tea-utils v1.3.6 // indirect
github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.62.545 // indirect
github.com/aliyun/aliyun-oss-go-sdk v2.2.2+incompatible // indirect
github.com/aliyun/credentials-go v1.3.10 // indirect
github.com/apistd/uni-go-sdk v0.0.2 // indirect
github.com/atc0005/go-teams-notify/v2 v2.13.0 // indirect
github.com/baidubce/bce-sdk-go v0.9.156 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blinkbean/dingtalk v0.0.0-20210905093040-7d935c0f7e19 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bwmarrin/discordgo v0.27.1 // indirect
github.com/casdoor/casdoor-go-sdk v0.50.0 // indirect
github.com/casdoor/go-reddit/v2 v2.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/clbanning/mxj/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cschomburg/go-pushbullet v0.0.0-20171206132031-67759df45fbb // indirect
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/dghubble/oauth1 v0.7.2 // indirect
github.com/dghubble/sling v1.4.0 // indirect
github.com/di-wu/parser v0.2.2 // indirect
github.com/di-wu/xsd-datetime v1.0.0 // indirect
github.com/drswork/go-twitter v0.0.0-20221107160839-dea1b6ed53d7 // indirect
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fxamacker/cbor/v2 v2.6.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.0 // indirect
github.com/go-lark/lark v1.9.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-webauthn/x v0.1.9 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/gomodule/redigo v2.0.0+incompatible // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/gregdel/pushover v1.2.1 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/line/line-bot-sdk-go v7.8.0+incompatible // indirect
github.com/markbates/going v1.0.0 // indirect
github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-ieproxy v0.0.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mileusna/viber v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mrjones/oauth v0.0.0-20180629183705-f4e24b6d100c // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/pingcap/errors v0.11.5-0.20210425183316-da1aaba5fb63 // indirect
github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7 // indirect
github.com/pingcap/tidb/parser v0.0.0-20221126021158-6b02a5d8ba7d // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/common v0.30.0 // indirect
github.com/prometheus/procfs v0.7.3 // indirect
github.com/qiniu/go-sdk/v7 v7.12.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
github.com/rs/zerolog v1.30.0 // indirect
github.com/scim2/filter-parser/v2 v2.2.0 // indirect
github.com/sendgrid/rest v2.6.9+incompatible // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect
github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/slack-go/slack v0.12.3 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.744 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.0.744 // indirect
github.com/tidwall/gjson v1.16.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/tklauser/go-sysconf v0.3.10 // indirect
github.com/tklauser/numcpus v0.4.0 // indirect
github.com/twilio/twilio-go v1.13.0 // indirect
github.com/ucloud/ucloud-sdk-go v0.22.5 // indirect
github.com/utahta/go-linenotify v0.5.0 // indirect
github.com/volcengine/volc-sdk-golang v1.0.117 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.mau.fi/util v0.0.0-20230805171708-199bf3eec776 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
golang.org/x/image v0.0.0-20190802002840-cff245a6509b // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.23.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405 // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.1.1 // indirect
maunium.net/go/maulogger/v2 v2.4.1 // indirect
modernc.org/cc/v3 v3.37.0 // indirect
modernc.org/ccgo/v3 v3.16.9 // indirect
modernc.org/libc v1.18.0 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.3.0 // indirect
modernc.org/opt v0.1.1 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)

1072
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -84,6 +84,10 @@ func getAllFilePathsInFolder(folder string, fileSuffix string) []string {
return err
}
if strings.HasSuffix(path, "node_modules") {
return filepath.SkipDir
}
if !strings.HasSuffix(info.Name(), fileSuffix) {
return nil
}
@ -99,7 +103,7 @@ func getAllFilePathsInFolder(folder string, fileSuffix string) []string {
return res
}
func parseEnData(category string) *I18nData {
func parseAllWords(category string) *I18nData {
var paths []string
if category == "backend" {
paths = getAllFilePathsInFolder("../", ".go")
@ -136,3 +140,11 @@ func parseEnData(category string) *I18nData {
return &data
}
func applyToOtherLanguage(category string, language string, newData *I18nData) {
oldData := readI18nFile(category, language)
println(oldData)
applyData(newData, oldData)
writeI18nFile(category, language, newData)
}

View File

@ -12,42 +12,69 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !skipCi
// +build !skipCi
package i18n
import (
"testing"
)
func applyToOtherLanguage(category string, language string, i18nData *I18nData) {
newData := readI18nFile(category, language)
println(newData)
applyData(i18nData, newData)
writeI18nFile(category, language, i18nData)
}
import "testing"
func TestGenerateI18nFrontend(t *testing.T) {
enData := parseEnData("frontend")
writeI18nFile("frontend", "en", enData)
data := parseAllWords("frontend")
applyToOtherLanguage("frontend", "de", enData)
applyToOtherLanguage("frontend", "es", enData)
applyToOtherLanguage("frontend", "fr", enData)
applyToOtherLanguage("frontend", "ja", enData)
applyToOtherLanguage("frontend", "ko", enData)
applyToOtherLanguage("frontend", "ru", enData)
applyToOtherLanguage("frontend", "zh", enData)
applyToOtherLanguage("frontend", "en", data)
applyToOtherLanguage("frontend", "zh", data)
applyToOtherLanguage("frontend", "es", data)
applyToOtherLanguage("frontend", "fr", data)
applyToOtherLanguage("frontend", "de", data)
applyToOtherLanguage("frontend", "id", data)
applyToOtherLanguage("frontend", "ja", data)
applyToOtherLanguage("frontend", "ko", data)
applyToOtherLanguage("frontend", "ru", data)
applyToOtherLanguage("frontend", "vi", data)
applyToOtherLanguage("frontend", "pt", data)
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)
applyToOtherLanguage("frontend", "cs", data)
applyToOtherLanguage("frontend", "sk", data)
}
func TestGenerateI18nBackend(t *testing.T) {
enData := parseEnData("backend")
writeI18nFile("backend", "en", enData)
data := parseAllWords("backend")
applyToOtherLanguage("backend", "de", enData)
applyToOtherLanguage("backend", "es", enData)
applyToOtherLanguage("backend", "fr", enData)
applyToOtherLanguage("backend", "ja", enData)
applyToOtherLanguage("backend", "ko", enData)
applyToOtherLanguage("backend", "ru", enData)
applyToOtherLanguage("backend", "zh", enData)
applyToOtherLanguage("backend", "en", data)
applyToOtherLanguage("backend", "zh", data)
applyToOtherLanguage("backend", "es", data)
applyToOtherLanguage("backend", "fr", data)
applyToOtherLanguage("backend", "de", data)
applyToOtherLanguage("backend", "id", data)
applyToOtherLanguage("backend", "ja", data)
applyToOtherLanguage("backend", "ko", data)
applyToOtherLanguage("backend", "ru", data)
applyToOtherLanguage("backend", "vi", data)
applyToOtherLanguage("backend", "pt", data)
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)
applyToOtherLanguage("backend", "cs", data)
applyToOtherLanguage("backend", "sk", data)
}

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

@ -0,0 +1,190 @@
{
"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",
"DeviceCode Invalid": "DeviceCode Invalid",
"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 LDAP is not enabled for the application": "The login method: login with LDAP is not enabled for the application",
"The login method: login with SMS is not enabled for the application": "The login method: login with SMS is not enabled for the application",
"The login method: login with email is not enabled for the application": "The login method: login with email is not enabled for the application",
"The login method: login with face is not enabled for the application": "The login method: login with face is not enabled for the application",
"The login method: login with password is not enabled for the application": "The login method: login with password is not enabled for the application",
"The organization: %s does not exist": "The organization: %s does not exist",
"The provider: %s does not exist": "The provider: %s does not exist",
"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",
"UserCode Expired": "UserCode Expired",
"UserCode Invalid": "UserCode Invalid",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing",
"the application for user %s is not found": "the application for user %s is not found",
"the organization: %s is not found": "the organization: %s is not found"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
},
"check": {
"%s does not meet the CIDR format requirements: %s": "%s does not meet the CIDR format requirements: %s",
"Affiliation cannot be blank": "Affiliation cannot be blank",
"CIDR for IP: %s should not be empty": "CIDR for IP: %s should not be empty",
"Default code does not match the code's matching rules": "Default code does not match the code's matching rules",
"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.",
"Face data does not exist, cannot log in": "Face data does not exist, cannot log in",
"Face data mismatch": "Face data mismatch",
"Failed to parse client IP: %s": "Failed to parse client IP: %s",
"FirstName cannot be blank": "FirstName cannot be blank",
"Invitation code cannot be blank": "Invitation code cannot be blank",
"Invitation code exhausted": "Invitation code exhausted",
"Invitation code is invalid": "Invitation code is invalid",
"Invitation code suspended": "Invitation code suspended",
"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 cannot be empty": "Password cannot be empty",
"Phone already exists": "Phone already exists",
"Phone cannot be empty": "Phone cannot be empty",
"Phone number is invalid": "Phone number is invalid",
"Please register using the email corresponding to the invitation code": "Please register using the email corresponding to the invitation code",
"Please register using the phone corresponding to the invitation code": "Please register using the phone corresponding to the invitation code",
"Please register using the username corresponding to the invitation code": "Please register using the username corresponding to the invitation code",
"Session outdated, please login again": "Session outdated, please login again",
"The invitation code has already been used": "The invitation code has already been used",
"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.",
"The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex": "The value \\\"%s\\\" for account field \\\"%s\\\" doesn't match the account item regex",
"The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"": "The value \\\"%s\\\" for signup field \\\"%s\\\" doesn't match the signup item regex of the application \\\"%s\\\"",
"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 255 characters).": "Username is too long (maximum is 255 characters).",
"Username must have at least 2 characters": "Username must have at least 2 characters",
"Username supports email format. Also 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. Also pay attention to the email format.": "Username supports email format. Also 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. Also pay attention to the email format.",
"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 IP address: %s has been banned according to the configuration of: ": "Your IP address: %s has been banned according to the configuration of: ",
"Your password has expired. Please reset your password by clicking \\\"Forgot password\\\"": "Your password has expired. Please reset your password by clicking \\\"Forgot password\\\"",
"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"
},
"enforcer": {
"the adapter: %s is not found": "the adapter: %s is not found"
},
"general": {
"Failed to import groups": "Failed to import groups",
"Failed to import users": "Failed to import users",
"Missing parameter": "Missing parameter",
"Only admin user can specify user": "Only admin user can specify user",
"Please login first": "Please login first",
"The organization: %s should have one application at least": "The organization: %s should have one application at least",
"The user: %s doesn't exist": "The user: %s doesn't exist",
"Wrong userId": "Wrong userId",
"don't support captchaProvider: ": "don't support captchaProvider: ",
"this operation is not allowed in demo mode": "this operation is not allowed in demo mode",
"this operation requires administrator to perform": "this operation requires administrator to perform"
},
"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.",
"adding a new user to the 'built-in' organization is currently disabled. Please note: all users in the 'built-in' organization are global administrators in Casdoor. Refer to the docs: https://casdoor.org/docs/basic/core-concepts#how-does-casdoor-manage-itself. If you still wish to create a user for the 'built-in' organization, go to the organization's settings page and enable the 'Has privilege consent' option.": "adding a new user to the 'built-in' organization is currently disabled. Please note: all users in the 'built-in' organization are global administrators in Casdoor. Refer to the docs: https://casdoor.org/docs/basic/core-concepts#how-does-casdoor-manage-itself. If you still wish to create a user for the 'built-in' organization, go to the organization's settings page and enable the 'Has privilege consent' option."
},
"permission": {
"The permission: \\\"%s\\\" doesn't exist": "The permission: \\\"%s\\\" doesn't exist"
},
"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"
},
"subscription": {
"Error": "Error"
},
"token": {
"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",
"MFA email is enabled but email is empty": "MFA email is enabled but email is empty",
"MFA phone is enabled but phone number is empty": "MFA phone is enabled but phone number is empty",
"New password cannot contain blank space.": "New password cannot contain blank space.",
"the user's owner and name should not be empty": "the user's owner and name should not be empty"
},
"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": {
"Invalid captcha provider.": "Invalid captcha provider.",
"Phone number is invalid in your region %s": "Phone number is invalid in your region %s",
"The verification code has already been used!": "The verification code has already been used!",
"The verification code has not been sent yet!": "The verification code has not been sent yet!",
"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!",
"please add a SMS provider to the \\\"Providers\\\" list for the application: %s": "please add a SMS provider to the \\\"Providers\\\" list for the application: %s",
"please add an Email provider to the \\\"Providers\\\" list for the application: %s": "please add an Email provider to the \\\"Providers\\\" list for the application: %s",
"the user does not exist, please sign up first": "the user does not exist, please sign up first"
},
"webauthn": {
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}

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