Compare commits

...

72 Commits

Author SHA1 Message Date
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
180 changed files with 11631 additions and 2315 deletions

View File

@ -87,6 +87,8 @@ p, *, *, GET, /api/get-prometheus-info, *, *
p, *, *, *, /api/metrics, *, * p, *, *, *, /api/metrics, *, *
p, *, *, GET, /api/get-pricing, *, * p, *, *, GET, /api/get-pricing, *, *
p, *, *, GET, /api/get-plan, *, * 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-organization-names, *, *
` `
@ -119,6 +121,10 @@ func IsAllowed(subOwner string, subName string, method string, urlPath string, o
panic(err) panic(err)
} }
if subOwner == "app" {
return true
}
if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) { if user != nil && user.IsAdmin && (subOwner == objOwner || (objOwner == "admin")) {
return true return true
} }

View File

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

48
conf/conf_quota.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conf
import (
"encoding/json"
"github.com/beego/beego"
)
type Quota struct {
Organization int `json:"organization"`
User int `json:"user"`
Application int `json:"application"`
Provider int `json:"provider"`
}
var quota = &Quota{-1, -1, -1, -1}
func init() {
initQuota()
}
func initQuota() {
res := beego.AppConfig.String("quota")
if res != "" {
err := json.Unmarshal([]byte(res), quota)
if err != nil {
panic(err)
}
}
}
func GetConfigQuota() *Quota {
return quota
}

View File

@ -140,25 +140,28 @@ func (c *ApiController) Signup() {
username = id username = id
} }
password := authForm.Password
msg = object.CheckPasswordComplexityByOrg(organization, password)
if msg != "" {
c.ResponseError(msg)
return
}
initScore, err := organization.GetInitScore() initScore, err := organization.GetInitScore()
if err != nil { if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error()) c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
return 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{ user := &object.User{
Owner: authForm.Organization, Owner: authForm.Organization,
Name: username, Name: username,
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
Id: id, Id: id,
Type: "normal-user", Type: userType,
Password: authForm.Password, Password: authForm.Password,
DisplayName: authForm.Name, DisplayName: authForm.Name,
Avatar: organization.DefaultAvatar, Avatar: organization.DefaultAvatar,
@ -210,7 +213,7 @@ func (c *ApiController) Signup() {
return return
} }
if application.HasPromptPage() { if application.HasPromptPage() && user.Type == "normal-user" {
// The prompt page needs the user to be signed in // The prompt page needs the user to be signed in
c.SetSessionUsername(user.GetId()) c.SetSessionUsername(user.GetId())
} }
@ -227,15 +230,6 @@ func (c *ApiController) Signup() {
return return
} }
isSignupFromPricing := authForm.Plan != "" && authForm.Pricing != ""
if isSignupFromPricing {
_, err = object.Subscribe(organization.Name, user.Name, authForm.Plan, authForm.Pricing)
if err != nil {
c.ResponseError(err.Error())
return
}
}
record := object.NewRecord(c.Ctx) record := object.NewRecord(c.Ctx)
record.Organization = application.Organization record.Organization = application.Organization
record.User = user.Name record.User = user.Name

View File

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

View File

@ -78,6 +78,46 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
} }
} }
// check whether paid-user have active subscription
if user.Type == "paid-user" {
subscriptions, err := object.GetSubscriptionsByUser(user.Owner, user.Name)
if err != nil {
c.ResponseError(err.Error())
return
}
existActiveSubscription := false
for _, subscription := range subscriptions {
if subscription.State == object.SubStateActive {
existActiveSubscription = true
break
}
}
if !existActiveSubscription {
// check pending subscription
for _, sub := range subscriptions {
if sub.State == object.SubStatePending {
c.ResponseOk("BuyPlanResult", sub)
return
}
}
// paid-user does not have active or pending subscription, find the default pricing of application
pricing, err := object.GetApplicationDefaultPricing(application.Organization, application.Name)
if err != nil {
c.ResponseError(err.Error())
return
}
if pricing == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:paid-user %s does not have active or pending subscription and the application: %s does not have default pricing"), user.Name, application.Name))
return
} else {
// let the paid-user select plan
c.ResponseOk("SelectPlan", pricing)
return
}
}
}
if form.Type == ResponseTypeLogin { if form.Type == ResponseTypeLogin {
c.SetSessionUsername(userId) c.SetSessionUsername(userId)
util.LogInfo(c.Ctx, "API: [%s] signed in", userId) util.LogInfo(c.Ctx, "API: [%s] signed in", userId)

View File

@ -61,6 +61,10 @@ func (c *ApiController) IsAdminOrSelf(user2 *object.User) bool {
return true return true
} }
if user == nil || user2 == nil {
return false
}
if user.Owner == user2.Owner && user.Name == user2.Name { if user.Owner == user2.Owner && user.Name == user2.Name {
return true return true
} }

View File

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

View File

@ -83,7 +83,7 @@ func (c *ApiController) GetEnforcer() {
return return
} }
if loadModelCfg == "true" { if loadModelCfg == "true" && enforcer.Model != "" {
err := enforcer.LoadModelCfg() err := enforcer.LoadModelCfg()
if err != nil { if err != nil {
return return

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"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
@ -23,7 +23,9 @@ import "github.com/casdoor/casdoor/object"
// @Success 200 {object} controllers.Response The Response object // @Success 200 {object} controllers.Response The Response object
// @router /get-dashboard [get] // @router /get-dashboard [get]
func (c *ApiController) GetDashboard() { func (c *ApiController) GetDashboard() {
data, err := object.GetDashboard() owner := c.Input().Get("owner")
data, err := object.GetDashboard(owner)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

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

View File

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

View File

@ -16,6 +16,7 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/beego/beego/utils/pagination" "github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
@ -82,7 +83,10 @@ func (c *ApiController) GetPlan() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if plan == nil {
c.ResponseError(fmt.Sprintf(c.T("plan:The plan: %s does not exist"), id))
return
}
if includeOption { if includeOption {
options, err := object.GetPermissionsByRole(plan.Role) options, err := object.GetPermissionsByRole(plan.Role)
if err != nil { if err != nil {
@ -110,14 +114,29 @@ func (c *ApiController) GetPlan() {
// @router /update-plan [post] // @router /update-plan [post]
func (c *ApiController) UpdatePlan() { func (c *ApiController) UpdatePlan() {
id := c.Input().Get("id") id := c.Input().Get("id")
owner := util.GetOwnerFromId(id)
var plan object.Plan var plan object.Plan
err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan) err := json.Unmarshal(c.Ctx.Input.RequestBody, &plan)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return 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.Data["json"] = wrapActionResponse(object.UpdatePlan(id, &plan))
c.ServeJSON() c.ServeJSON()
} }
@ -136,7 +155,14 @@ func (c *ApiController) AddPlan() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return 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.Data["json"] = wrapActionResponse(object.AddPlan(&plan))
c.ServeJSON() c.ServeJSON()
} }
@ -155,7 +181,13 @@ func (c *ApiController) DeletePlan() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return 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.Data["json"] = wrapActionResponse(object.DeletePlan(&plan))
c.ServeJSON() c.ServeJSON()
} }

View File

@ -16,6 +16,7 @@ package controllers
import ( import (
"encoding/json" "encoding/json"
"fmt"
"github.com/beego/beego/utils/pagination" "github.com/beego/beego/utils/pagination"
"github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/object"
@ -80,7 +81,10 @@ func (c *ApiController) GetPricing() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
if pricing == nil {
c.ResponseError(fmt.Sprintf(c.T("pricing:The pricing: %s does not exist"), id))
return
}
c.ResponseOk(pricing) c.ResponseOk(pricing)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -258,6 +258,13 @@ func (c *ApiController) UpdateUser() {
return return
} }
if c.Input().Get("allowEmpty") == "" {
if user.DisplayName == "" {
c.ResponseError(c.T("user:Display name cannot be empty"))
return
}
}
if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" { if msg := object.CheckUpdateUser(oldUser, &user, c.GetAcceptLanguage()); msg != "" {
c.ResponseError(msg) c.ResponseError(msg)
return return
@ -441,6 +448,10 @@ func (c *ApiController) SetPassword() {
} }
targetUser, err := object.GetUser(userId) 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 { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

@ -48,17 +48,17 @@ func (c *ApiController) UploadUsers() {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return 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) path := util.GetUploadXlsxPath(fileId)
util.EnsureFileFolderExists(path) defer os.Remove(path)
err = saveFile(path, &file) err = saveFile(path, &file)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return
} }
affected, err := object.UploadUsers(owner, fileId) affected, err := object.UploadUsers(owner, path)
if err != nil { if err != nil {
c.ResponseError(err.Error()) c.ResponseError(err.Error())
return return

View File

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

26
go.mod
View File

@ -6,16 +6,17 @@ require (
github.com/Masterminds/squirrel v1.5.3 github.com/Masterminds/squirrel v1.5.3
github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b
github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387 github.com/alexedwards/argon2id v0.0.0-20211130144151-3585854a6387
github.com/aliyun/alibaba-cloud-sdk-go v1.62.188 // indirect github.com/aws/aws-sdk-go v1.45.5
github.com/aws/aws-sdk-go v1.44.4
github.com/beego/beego v1.12.12 github.com/beego/beego v1.12.12
github.com/beevik/etree v1.1.0 github.com/beevik/etree v1.1.0
github.com/casbin/casbin v1.9.1 // indirect github.com/casbin/casbin v1.9.1 // indirect
github.com/casbin/casbin/v2 v2.30.1 github.com/casbin/casbin/v2 v2.37.0
github.com/casdoor/go-sms-sender v0.12.0 github.com/casdoor/go-sms-sender v0.14.0
github.com/casdoor/gomail/v2 v2.0.1 github.com/casdoor/gomail/v2 v2.0.1
github.com/casdoor/notify v0.43.0
github.com/casdoor/oss v1.3.0 github.com/casdoor/oss v1.3.0
github.com/casdoor/xorm-adapter/v3 v3.0.4 github.com/casdoor/xorm-adapter/v3 v3.0.4
github.com/casvisor/casvisor-go-sdk v1.0.3
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/denisenkom/go-mssqldb v0.9.0 github.com/denisenkom/go-mssqldb v0.9.0
github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect github.com/elazarl/go-bindata-assetfs v1.0.1 // indirect
@ -26,17 +27,16 @@ require (
github.com/go-mysql-org/go-mysql v1.7.0 github.com/go-mysql-org/go-mysql v1.7.0
github.com/go-pay/gopay v1.5.72 github.com/go-pay/gopay v1.5.72
github.com/go-sql-driver/mysql v1.6.0 github.com/go-sql-driver/mysql v1.6.0
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/go-webauthn/webauthn v0.6.0 github.com/go-webauthn/webauthn v0.6.0
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.1
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v1.2.21 github.com/lestrrat-go/jwx v1.2.21
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
github.com/markbates/goth v1.75.2 github.com/markbates/goth v1.75.2
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/nikoksr/notify v0.41.0 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
github.com/nyaruka/phonenumbers v1.1.5 github.com/nyaruka/phonenumbers v1.1.5
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
@ -46,13 +46,12 @@ require (
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/russellhaering/gosaml2 v0.9.0 github.com/russellhaering/gosaml2 v0.9.0
github.com/russellhaering/goxmldsig v1.2.0 github.com/russellhaering/goxmldsig v1.2.0
github.com/satori/go.uuid v1.2.0
github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed github.com/siddontang/go-log v0.0.0-20190221022429-1e957dd83bed
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/stretchr/testify v1.8.4 github.com/stretchr/testify v1.8.4
github.com/stripe/stripe-go/v74 v74.29.0 // indirect github.com/stripe/stripe-go/v74 v74.29.0
github.com/tealeg/xlsx v1.0.5 github.com/tealeg/xlsx v1.0.5
github.com/thanhpk/randstr v1.0.4 github.com/thanhpk/randstr v1.0.4
github.com/tklauser/go-sysconf v0.3.10 // indirect github.com/tklauser/go-sysconf v0.3.10 // indirect
@ -60,11 +59,12 @@ require (
github.com/xorm-io/core v0.7.4 github.com/xorm-io/core v0.7.4
github.com/xorm-io/xorm v1.1.6 github.com/xorm-io/xorm v1.1.6
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/crypto v0.11.0 golang.org/x/crypto v0.12.0
golang.org/x/net v0.13.0 golang.org/x/net v0.14.0
golang.org/x/oauth2 v0.10.0 golang.org/x/oauth2 v0.11.0
google.golang.org/api v0.138.0
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0 // indirect maunium.net/go/mautrix v0.16.0
modernc.org/sqlite v1.18.2 modernc.org/sqlite v1.18.2
) )

394
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,13 @@ func TestGenerateI18nFrontend(t *testing.T) {
applyToOtherLanguage("frontend", "tr", data) applyToOtherLanguage("frontend", "tr", data)
applyToOtherLanguage("frontend", "ar", data) applyToOtherLanguage("frontend", "ar", data)
applyToOtherLanguage("frontend", "he", data) applyToOtherLanguage("frontend", "he", data)
applyToOtherLanguage("frontend", "nl", data)
applyToOtherLanguage("frontend", "pl", data)
applyToOtherLanguage("frontend", "fi", data) applyToOtherLanguage("frontend", "fi", data)
applyToOtherLanguage("frontend", "sv", data)
applyToOtherLanguage("frontend", "uk", data)
applyToOtherLanguage("frontend", "kk", data)
applyToOtherLanguage("frontend", "fa", data)
} }
func TestGenerateI18nBackend(t *testing.T) { func TestGenerateI18nBackend(t *testing.T) {
@ -60,5 +66,11 @@ func TestGenerateI18nBackend(t *testing.T) {
applyToOtherLanguage("backend", "tr", data) applyToOtherLanguage("backend", "tr", data)
applyToOtherLanguage("backend", "ar", data) applyToOtherLanguage("backend", "ar", data)
applyToOtherLanguage("backend", "he", data) applyToOtherLanguage("backend", "he", data)
applyToOtherLanguage("backend", "nl", data)
applyToOtherLanguage("backend", "pl", data)
applyToOtherLanguage("backend", "fi", data) applyToOtherLanguage("backend", "fi", data)
applyToOtherLanguage("backend", "sv", data)
applyToOtherLanguage("backend", "uk", data)
applyToOtherLanguage("backend", "kk", data)
applyToOtherLanguage("backend", "fa", data)
} }

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

@ -9,11 +9,11 @@
"passwordType": "plain", "passwordType": "plain",
"passwordSalt": "", "passwordSalt": "",
"passwordOptions": ["AtLeast6"], "passwordOptions": ["AtLeast6"],
"countryCodes": ["US", "ES", "CN", "FR", "DE", "GB", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR", "DZ", "IL", "PH"], "countryCodes": ["US", "GB", "ES", "FR", "DE", "CN", "JP", "KR", "VN", "ID", "SG", "IN", "IT", "MY", "TR", "DZ", "IL", "PH", "NL", "PL", "FI", "SE", "UA", "KZ"],
"defaultAvatar": "", "defaultAvatar": "",
"defaultApplication": "", "defaultApplication": "",
"tags": [], "tags": [],
"languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "fi"], "languages": ["en", "zh", "es", "fr", "de", "id", "ja", "ko", "ru", "vi", "it", "ms", "tr","ar", "he", "nl", "pl", "fi", "sv", "uk", "kk", "fa"],
"masterPassword": "", "masterPassword": "",
"initScore": 2000, "initScore": 2000,
"enableSoftDeletion": false, "enableSoftDeletion": false,

15
main.go
View File

@ -15,7 +15,6 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"github.com/beego/beego" "github.com/beego/beego"
@ -30,17 +29,10 @@ import (
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
) )
func getCreateDatabaseFlag() bool {
res := flag.Bool("createDatabase", false, "true if you need Casdoor to create database")
flag.Parse()
return *res
}
func main() { func main() {
createDatabase := getCreateDatabaseFlag() object.InitFlag()
object.InitAdapter()
object.InitAdapter(createDatabase) object.CreateTables()
object.CreateTables(createDatabase)
object.DoMigration() object.DoMigration()
object.InitDb() object.InitDb()
@ -50,6 +42,7 @@ func main() {
proxy.InitHttpClient() proxy.InitHttpClient()
authz.InitApi() authz.InitApi()
object.InitUserManager() object.InitUserManager()
object.InitCasvisorConfig()
util.SafeGoroutine(func() { object.RunSyncUsersJob() }) util.SafeGoroutine(func() { object.RunSyncUsersJob() })

29
notification/bark.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/bark"
)
func NewBarkProvider(deviceKey string) (notify.Notifier, error) {
barkSrv := bark.New(deviceKey)
notifier := notify.New()
notifier.UseServices(barkSrv)
return notifier, nil
}

View File

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

33
notification/dingtalk.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/dingding"
)
func NewDingTalkProvider(token string, secret string) (notify.Notifier, error) {
cfg := dingding.Config{
Token: token,
Secret: secret,
}
dingtalkSrv := dingding.New(&cfg)
notifier := notify.New()
notifier.UseServices(dingtalkSrv)
return notifier, nil
}

37
notification/discord.go Normal file
View File

@ -0,0 +1,37 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/discord"
)
func NewDiscordProvider(token string, channelId string) (*notify.Notify, error) {
discordSrv := discord.New()
err := discordSrv.AuthenticateWithBotToken(token)
if err != nil {
return nil, err
}
discordSrv.SetHttpClient(proxy.ProxyHttpClient)
discordSrv.AddReceivers(channelId)
notifier := notify.NewWithServices(discordSrv)
return notifier, nil
}

View File

@ -0,0 +1,53 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"context"
"strings"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/googlechat"
"google.golang.org/api/chat/v1"
"google.golang.org/api/option"
)
func NewGoogleChatProvider(credentials string) (*notify.Notify, error) {
withCred := option.WithCredentialsJSON([]byte(credentials))
withSpacesScope := option.WithScopes("https://www.googleapis.com/auth/chat.spaces")
listSvc, err := chat.NewService(context.Background(), withCred, withSpacesScope)
spaces, err := listSvc.Spaces.List().Do()
if err != nil {
return nil, err
}
receivers := make([]string, 0)
for _, space := range spaces.Spaces {
name := strings.Replace(space.Name, "spaces/", "", 1)
receivers = append(receivers, name)
}
googleChatSrv, err := googlechat.New(withCred)
if err != nil {
return nil, err
}
googleChatSrv.AddReceivers(receivers...)
notifier := notify.NewWithServices(googleChatSrv)
return notifier, nil
}

29
notification/lark.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/lark"
)
func NewLarkProvider(webhookURL string) (notify.Notifier, error) {
larkSrv := lark.NewWebhookService(webhookURL)
notifier := notify.New()
notifier.UseServices(larkSrv)
return notifier, nil
}

32
notification/line.go Normal file
View File

@ -0,0 +1,32 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/line"
)
func NewLineProvider(channelSecret string, accessToken string, receiver string) (*notify.Notify, error) {
lineSrv, _ := line.NewWithHttpClient(channelSecret, accessToken, proxy.ProxyHttpClient)
lineSrv.AddReceivers(receiver)
notifier := notify.New()
notifier.UseServices(lineSrv)
return notifier, nil
}

36
notification/matrix.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/matrix"
"maunium.net/go/mautrix/id"
)
func NewMatrixProvider(userId string, roomId string, accessToken string, homeServer string) (*notify.Notify, error) {
matrixSrv, err := matrix.New(id.UserID(userId), id.RoomID(roomId), homeServer, accessToken)
if err != nil {
return nil, err
}
matrixSrv.SetHttpClient(proxy.ProxyHttpClient)
notifier := notify.New()
notifier.UseServices(matrixSrv)
return notifier, nil
}

View File

@ -0,0 +1,31 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/msteams"
)
func NewMicrosoftTeamsProvider(webhookURL string) (notify.Notifier, error) {
msTeamsSrv := msteams.New()
msTeamsSrv.AddReceivers(webhookURL)
notifier := notify.New()
notifier.UseServices(msTeamsSrv)
return notifier, nil
}

View File

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

View File

@ -0,0 +1,31 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/pushbullet"
)
func NewPushbulletProvider(apiToken string, deviceNickname string) (notify.Notifier, error) {
pushbulletSrv := pushbullet.New(apiToken)
pushbulletSrv.AddReceivers(deviceNickname)
notifier := notify.New()
notifier.UseServices(pushbulletSrv)
return notifier, nil
}

31
notification/pushover.go Normal file
View File

@ -0,0 +1,31 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/pushover"
)
func NewPushoverProvider(appToken string, recipientID string) (notify.Notifier, error) {
pushoverSrv := pushover.New(appToken)
pushoverSrv.AddReceivers(recipientID)
notifier := notify.New()
notifier.UseServices(pushoverSrv)
return notifier, nil
}

34
notification/reddit.go Normal file
View File

@ -0,0 +1,34 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/reddit"
)
func NewRedditProvider(clientId string, clientSecret string, username string, password string, recipient string) (notify.Notifier, error) {
redditSrv, err := reddit.New(clientId, clientSecret, username, password)
if err != nil {
return nil, err
}
redditSrv.AddReceivers(recipient)
notifier := notify.New()
notifier.UseServices(redditSrv)
return notifier, nil
}

View File

@ -0,0 +1,47 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"fmt"
"strings"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/rocketchat"
)
func NewRocketChatProvider(clientId string, clientSecret string, endpoint string, channelName string) (notify.Notifier, error) {
parts := strings.Split(endpoint, "://")
var scheme, serverURL string
if len(parts) >= 2 {
scheme = parts[0]
serverURL = parts[1]
} else {
return nil, fmt.Errorf("parse endpoint error")
}
rocketChatSrv, err := rocketchat.New(serverURL, scheme, clientId, clientSecret)
if err != nil {
return nil, err
}
rocketChatSrv.AddReceivers(channelName)
notifier := notify.New()
notifier.UseServices(rocketChatSrv)
return notifier, nil
}

30
notification/slack.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/slack"
)
func NewSlackProvider(apiToken string, channelID string) (*notify.Notify, error) {
slackSrv := slack.New(apiToken)
slackSrv.AddReceivers(channelID)
notifier := notify.New()
notifier.UseServices(slackSrv)
return notifier, nil
}

View File

@ -18,9 +18,9 @@ import (
"strconv" "strconv"
"github.com/casdoor/casdoor/proxy" "github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/telegram"
api "github.com/go-telegram-bot-api/telegram-bot-api" api "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/nikoksr/notify"
"github.com/nikoksr/notify/service/telegram"
) )
func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, error) { func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, error) {
@ -28,15 +28,18 @@ func NewTelegramProvider(apiToken string, chatIdStr string) (notify.Notifier, er
if err != nil { if err != nil {
return nil, err return nil, err
} }
t := &telegram.Telegram{} telegramSrv := &telegram.Telegram{}
t.SetClient(client) telegramSrv.SetClient(client)
chatId, err := strconv.ParseInt(chatIdStr, 10, 64) chatId, err := strconv.ParseInt(chatIdStr, 10, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }
t.AddReceivers(chatId) telegramSrv.AddReceivers(chatId)
return t, nil notifier := notify.New()
notifier.UseServices(telegramSrv)
return notifier, nil
} }

41
notification/twitter.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/twitter"
)
func NewTwitterProvider(consumerKey string, consumerSecret string, accessToken string, accessTokenSecret string, twitterId string) (*notify.Notify, error) {
credentials := twitter.Credentials{
ConsumerKey: consumerKey,
ConsumerSecret: consumerSecret,
AccessToken: accessToken,
AccessTokenSecret: accessTokenSecret,
}
twitterSrv, err := twitter.NewWithHttpClient(credentials, proxy.ProxyHttpClient)
if err != nil {
return nil, err
}
twitterSrv.AddReceivers(twitterId)
notifier := notify.New()
notifier.UseServices(twitterSrv)
return notifier, nil
}

36
notification/viber.go Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/viber"
)
func NewViberProvider(senderName string, appKey string, webhookURL string, receiverId string) (notify.Notifier, error) {
viberSrv := viber.New(appKey, senderName, "")
err := viberSrv.SetWebhook(webhookURL)
if err != nil {
return nil, err
}
viberSrv.AddReceivers(receiverId)
notifier := notify.New()
notifier.UseServices(viberSrv)
return notifier, nil
}

33
notification/webpush.go Normal file
View File

@ -0,0 +1,33 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package notification
import (
"github.com/casdoor/notify"
"github.com/casdoor/notify/service/webpush"
)
func NewWebpushProvider(publicKey string, privateKey string, endpoint string) (*notify.Notify, error) {
webpushSrv := webpush.New(publicKey, privateKey)
subscription := webpush.Subscription{
Endpoint: endpoint,
}
webpushSrv.AddReceivers(subscription)
notifier := notify.NewWithServices(webpushSrv)
return notifier, nil
}

View File

@ -151,7 +151,7 @@ func (adapter *Adapter) InitAdapter() error {
if adapter.Adapter == nil { if adapter.Adapter == nil {
var dataSourceName string var dataSourceName string
if adapter.builtInAdapter() { if adapter.isBuiltIn() {
dataSourceName = conf.GetConfigString("dataSourceName") dataSourceName = conf.GetConfigString("dataSourceName")
if adapter.DatabaseType == "mysql" { if adapter.DatabaseType == "mysql" {
dataSourceName = dataSourceName + adapter.Database dataSourceName = dataSourceName + adapter.Database
@ -183,6 +183,14 @@ func (adapter *Adapter) InitAdapter() error {
var err error var err error
engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName) engine, err := xorm.NewEngine(adapter.DatabaseType, dataSourceName)
if adapter.isBuiltIn() && adapter.DatabaseType == "postgres" {
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
if schema != "" {
engine.SetSchema(schema)
}
}
adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix) adapter.Adapter, err = xormadapter.NewAdapterByEngineWithTableName(engine, adapter.getTable(), adapter.TableNamePrefix)
if err != nil { if err != nil {
return err return err
@ -211,7 +219,7 @@ func adapterChangeTrigger(oldName string, newName string) error {
return session.Commit() return session.Commit()
} }
func (adapter *Adapter) builtInAdapter() bool { func (adapter *Adapter) isBuiltIn() bool {
if adapter.Owner != "built-in" { if adapter.Owner != "built-in" {
return false return false
} }

View File

@ -57,7 +57,9 @@ type Application struct {
SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"` SignupItems []*SignupItem `xorm:"varchar(1000)" json:"signupItems"`
GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"` GrantTypes []string `xorm:"varchar(1000)" json:"grantTypes"`
OrganizationObj *Organization `xorm:"-" json:"organizationObj"` OrganizationObj *Organization `xorm:"-" json:"organizationObj"`
CertPublicKey string `xorm:"-" json:"certPublicKey"`
Tags []string `xorm:"mediumtext" json:"tags"` Tags []string `xorm:"mediumtext" json:"tags"`
InvitationCodes []string `xorm:"varchar(200)" json:"invitationCodes"`
ClientId string `xorm:"varchar(100)" json:"clientId"` ClientId string `xorm:"varchar(100)" json:"clientId"`
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"` ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
@ -311,6 +313,11 @@ func GetMaskedApplication(application *Application, userId string) *Application
application.OrganizationObj.PasswordSalt = "***" application.OrganizationObj.PasswordSalt = "***"
} }
} }
if application.InvitationCodes != nil {
application.InvitationCodes = []string{"***"}
}
return application return application
} }
@ -421,15 +428,14 @@ func (application *Application) GetId() string {
} }
func (application *Application) IsRedirectUriValid(redirectUri string) bool { func (application *Application) IsRedirectUriValid(redirectUri string) bool {
isValid := false redirectUris := append([]string{"http://localhost:"}, application.RedirectUris...)
for _, targetUri := range application.RedirectUris { for _, targetUri := range redirectUris {
targetUriRegex := regexp.MustCompile(targetUri) targetUriRegex := regexp.MustCompile(targetUri)
if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) { if targetUriRegex.MatchString(redirectUri) || strings.Contains(redirectUri, targetUri) {
isValid = true return true
break
} }
} }
return isValid return false
} }
func IsOriginAllowed(origin string) (bool, error) { func IsOriginAllowed(origin string) (bool, error) {

View File

@ -66,8 +66,11 @@ func CheckUserSignup(application *Application, organization *Organization, form
} }
} }
if len(form.Password) <= 5 { if application.IsSignupItemVisible("Password") {
return i18n.Translate(lang, "check:Password must have at least 6 characters") msg := CheckPasswordComplexityByOrg(organization, form.Password)
if msg != "" {
return msg
}
} }
if application.IsSignupItemVisible("Email") { if application.IsSignupItemVisible("Email") {
@ -124,6 +127,18 @@ func CheckUserSignup(application *Application, organization *Organization, form
} }
} }
if len(application.InvitationCodes) > 0 {
if form.InvitationCode == "" {
if application.IsSignupItemRequired("Invitation code") {
return i18n.Translate(lang, "check:Invitation code cannot be blank")
}
} else {
if !util.InSlice(application.InvitationCodes, form.InvitationCode) {
return i18n.Translate(lang, "check:Invitation code is invalid")
}
}
}
return "" return ""
} }
@ -391,10 +406,6 @@ func CheckUsername(username string, lang string) string {
} }
func CheckUpdateUser(oldUser, user *User, lang string) string { func CheckUpdateUser(oldUser, user *User, lang string) string {
if user.DisplayName == "" {
return i18n.Translate(lang, "user:Display name cannot be empty")
}
if oldUser.Name != user.Name { if oldUser.Name != user.Name {
if msg := CheckUsername(user.Name, lang); msg != "" { if msg := CheckUsername(user.Name, lang); msg != "" {
return msg return msg
@ -404,7 +415,7 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
} }
} }
if oldUser.Email != user.Email { if oldUser.Email != user.Email {
if HasUserByField(user.Name, "email", user.Email) { if HasUserByField(user.Owner, "email", user.Email) {
return i18n.Translate(lang, "check:Email already exists") return i18n.Translate(lang, "check:Email already exists")
} }
} }

View File

@ -27,7 +27,7 @@ type Dashboard struct {
SubscriptionCounts []int `json:"subscriptionCounts"` SubscriptionCounts []int `json:"subscriptionCounts"`
} }
func GetDashboard() (*Dashboard, error) { func GetDashboard(owner string) (*Dashboard, error) {
dashboard := &Dashboard{ dashboard := &Dashboard{
OrganizationCounts: make([]int, 31), OrganizationCounts: make([]int, 31),
UserCounts: make([]int, 31), UserCounts: make([]int, 31),
@ -47,7 +47,7 @@ func GetDashboard() (*Dashboard, error) {
wg.Add(5) wg.Add(5)
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := ormer.Engine.Find(&organizations); err != nil { if err := ormer.Engine.Find(&organizations, &Organization{Owner: owner}); err != nil {
panic(err) panic(err)
} }
}() }()
@ -55,7 +55,7 @@ func GetDashboard() (*Dashboard, error) {
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := ormer.Engine.Find(&users); err != nil { if err := ormer.Engine.Find(&users, &User{Owner: owner}); err != nil {
panic(err) panic(err)
} }
}() }()
@ -63,7 +63,7 @@ func GetDashboard() (*Dashboard, error) {
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := ormer.Engine.Find(&providers); err != nil { if err := ormer.Engine.Find(&providers, &Provider{Owner: owner}); err != nil {
panic(err) panic(err)
} }
}() }()
@ -71,7 +71,7 @@ func GetDashboard() (*Dashboard, error) {
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := ormer.Engine.Find(&applications); err != nil { if err := ormer.Engine.Find(&applications, &Application{Owner: owner}); err != nil {
panic(err) panic(err)
} }
}() }()
@ -79,7 +79,7 @@ func GetDashboard() (*Dashboard, error) {
go func() { go func() {
defer wg.Done() defer wg.Done()
if err := ormer.Engine.Find(&subscriptions); err != nil { if err := ormer.Engine.Find(&subscriptions, &Subscription{Owner: owner}); err != nil {
panic(err) panic(err)
} }
}() }()

View File

@ -311,18 +311,20 @@ func SyncLdapUsers(owner string, syncUsers []LdapUser, ldapId string) (existUser
} }
newUser := &User{ newUser := &User{
Owner: owner, Owner: owner,
Name: name, Name: name,
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
DisplayName: syncUser.buildLdapDisplayName(), DisplayName: syncUser.buildLdapDisplayName(),
Avatar: organization.DefaultAvatar, SignupApplication: organization.DefaultApplication,
Email: syncUser.Email, Type: "normal-user",
Phone: syncUser.Mobile, Avatar: organization.DefaultAvatar,
Address: []string{syncUser.Address}, Email: syncUser.Email,
Affiliation: affiliation, Phone: syncUser.Mobile,
Tag: tag, Address: []string{syncUser.Address},
Score: score, Affiliation: affiliation,
Ldap: syncUser.Uuid, Tag: tag,
Score: score,
Ldap: syncUser.Uuid,
} }
affected, err := AddUser(newUser) affected, err := AddUser(newUser)

View File

@ -81,12 +81,15 @@ func (mfa *TotpMfa) SetupVerify(ctx *context.Context, passcode string) error {
return errors.New("totp secret is missing") return errors.New("totp secret is missing")
} }
result, _ := totp.ValidateCustom(passcode, secret.(string), time.Now().UTC(), totp.ValidateOpts{ result, err := totp.ValidateCustom(passcode, secret.(string), time.Now().UTC(), totp.ValidateOpts{
Period: MfaTotpPeriodInSeconds, Period: MfaTotpPeriodInSeconds,
Skew: 1, Skew: 1,
Digits: otp.DigitsSix, Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1, Algorithm: otp.AlgorithmSHA1,
}) })
if err != nil {
return err
}
if result { if result {
return nil return nil
@ -125,7 +128,15 @@ func (mfa *TotpMfa) Enable(ctx *context.Context, user *User) error {
} }
func (mfa *TotpMfa) Verify(passcode string) error { func (mfa *TotpMfa) Verify(passcode string) error {
result := totp.Validate(passcode, mfa.Config.Secret) result, err := totp.ValidateCustom(passcode, mfa.Config.Secret, time.Now().UTC(), totp.ValidateOpts{
Period: MfaTotpPeriodInSeconds,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
if err != nil {
return err
}
if result { if result {
return nil return nil

View File

@ -18,12 +18,12 @@ import (
"context" "context"
"github.com/casdoor/casdoor/notification" "github.com/casdoor/casdoor/notification"
"github.com/nikoksr/notify" "github.com/casdoor/notify"
) )
func getNotificationClient(provider *Provider) (notify.Notifier, error) { func getNotificationClient(provider *Provider) (notify.Notifier, error) {
var client notify.Notifier var client notify.Notifier
client, err := notification.GetNotificationProvider(provider.Type, provider.AppId, provider.Receiver) client, err := notification.GetNotificationProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.ClientId2, provider.ClientSecret2, provider.AppId, provider.Receiver, provider.Method, provider.Title, provider.Metadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -437,14 +437,6 @@ func organizationChangeTrigger(oldName string, newName string) error {
return err return err
} }
record := new(Record)
record.Owner = newName
record.Organization = newName
_, err = session.Where("organization=?", oldName).Update(record)
if err != nil {
return err
}
resource := new(Resource) resource := new(Resource)
resource.Owner = newName resource.Owner = newName
_, err = session.Where("owner=?", oldName).Update(resource) _, err = session.Where("owner=?", oldName).Update(resource)

View File

@ -16,7 +16,10 @@ package object
import ( import (
"database/sql" "database/sql"
"flag"
"fmt" "fmt"
"os"
"regexp"
"runtime" "runtime"
"strings" "strings"
@ -32,7 +35,24 @@ import (
_ "modernc.org/sqlite" // db = sqlite _ "modernc.org/sqlite" // db = sqlite
) )
var ormer *Ormer var (
ormer *Ormer = nil
isCreateDatabaseDefined = false
createDatabase = true
)
func InitFlag() {
if !isCreateDatabaseDefined {
isCreateDatabaseDefined = true
createDatabase = getCreateDatabaseFlag()
}
}
func getCreateDatabaseFlag() bool {
res := flag.Bool("createDatabase", false, "true if you need to create database")
flag.Parse()
return *res
}
func InitConfig() { func InitConfig() {
err := beego.LoadAppConfig("ini", "../conf/app.conf") err := beego.LoadAppConfig("ini", "../conf/app.conf")
@ -42,12 +62,23 @@ func InitConfig() {
beego.BConfig.WebConfig.Session.SessionOn = true beego.BConfig.WebConfig.Session.SessionOn = true
InitAdapter(true) InitAdapter()
CreateTables(true) CreateTables()
DoMigration() DoMigration()
} }
func InitAdapter(createDatabase bool) { func InitAdapter() {
if conf.GetConfigString("driverName") == "" {
if !util.FileExist("conf/app.conf") {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
dir = strings.ReplaceAll(dir, "\\", "/")
panic(fmt.Sprintf("The Casdoor config file: \"app.conf\" was not found, it should be placed at: \"%s/conf/app.conf\"", dir))
}
}
if createDatabase { if createDatabase {
err := createDatabaseForPostgres(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName")) err := createDatabaseForPostgres(conf.GetConfigString("driverName"), conf.GetConfigDataSourceName(), conf.GetConfigString("dbName"))
if err != nil { if err != nil {
@ -62,7 +93,7 @@ func InitAdapter(createDatabase bool) {
ormer.Engine.SetTableMapper(tbMapper) ormer.Engine.SetTableMapper(tbMapper)
} }
func CreateTables(createDatabase bool) { func CreateTables() {
if createDatabase { if createDatabase {
err := ormer.CreateDatabase() err := ormer.CreateDatabase()
if err != nil { if err != nil {
@ -105,9 +136,14 @@ func NewAdapter(driverName string, dataSourceName string, dbName string) *Ormer
return a return a
} }
func refineDataSourceNameForPostgres(dataSourceName string) string {
reg := regexp.MustCompile(`dbname=[^ ]+\s*`)
return reg.ReplaceAllString(dataSourceName, "")
}
func createDatabaseForPostgres(driverName string, dataSourceName string, dbName string) error { func createDatabaseForPostgres(driverName string, dataSourceName string, dbName string) error {
if driverName == "postgres" { if driverName == "postgres" {
db, err := sql.Open(driverName, dataSourceName) db, err := sql.Open(driverName, refineDataSourceNameForPostgres(dataSourceName))
if err != nil { if err != nil {
return err return err
} }
@ -119,6 +155,21 @@ func createDatabaseForPostgres(driverName string, dataSourceName string, dbName
return err return err
} }
} }
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
if schema != "" {
db, err = sql.Open(driverName, dataSourceName)
if err != nil {
return err
}
defer db.Close()
_, err = db.Exec(fmt.Sprintf("CREATE SCHEMA %s;", schema))
if err != nil {
if !strings.Contains(err.Error(), "already exists") {
return err
}
}
}
return nil return nil
} else { } else {
@ -151,6 +202,12 @@ func (a *Ormer) open() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
if a.driverName == "postgres" {
schema := util.GetValueFromDataSourceName("search_path", dataSourceName)
if schema != "" {
engine.SetSchema(schema)
}
}
a.Engine = engine a.Engine = engine
} }
@ -229,11 +286,6 @@ func (a *Ormer) createTable() {
panic(err) panic(err)
} }
err = a.Engine.Sync2(new(Record))
if err != nil {
panic(err)
}
err = a.Engine.Sync2(new(Webhook)) err = a.Engine.Sync2(new(Webhook))
if err != nil { if err != nil {
panic(err) panic(err)
@ -294,76 +346,3 @@ func (a *Ormer) createTable() {
panic(err) panic(err)
} }
} }
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := ormer.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
session = session.And("owner=?", owner)
}
if field != "" && value != "" {
if util.FilterField(field) {
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
return session
}
func GetSessionForUser(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := ormer.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
if offset == -1 {
session = session.And("owner=?", owner)
} else {
session = session.And("a.owner=?", owner)
}
}
if field != "" && value != "" {
if util.FilterField(field) {
if offset != -1 {
field = fmt.Sprintf("a.%s", field)
}
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tableName := tableNamePrefix + "user"
if offset == -1 {
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
} else {
if sortOrder == "ascend" {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Asc("a." + util.SnakeString(sortField))
} else {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Desc("a." + util.SnakeString(sortField))
}
}
return session
}

96
object/ormer_session.go Normal file
View File

@ -0,0 +1,96 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/xorm"
)
func GetSession(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := ormer.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
session = session.And("owner=?", owner)
}
if field != "" && value != "" {
if util.FilterField(field) {
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
return session
}
func GetSessionForUser(owner string, offset, limit int, field, value, sortField, sortOrder string) *xorm.Session {
session := ormer.Engine.Prepare()
if offset != -1 && limit != -1 {
session.Limit(limit, offset)
}
if owner != "" {
if offset == -1 {
session = session.And("owner=?", owner)
} else {
session = session.And("a.owner=?", owner)
}
}
if field != "" && value != "" {
if util.FilterField(field) {
if offset != -1 {
field = fmt.Sprintf("a.%s", field)
}
session = session.And(fmt.Sprintf("%s like ?", util.SnakeString(field)), fmt.Sprintf("%%%s%%", value))
}
}
if sortField == "" || sortOrder == "" {
sortField = "created_time"
}
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tableName := tableNamePrefix + "user"
if offset == -1 {
if sortOrder == "ascend" {
session = session.Asc(util.SnakeString(sortField))
} else {
session = session.Desc(util.SnakeString(sortField))
}
} else {
if sortOrder == "ascend" {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Asc("a." + util.SnakeString(sortField))
} else {
session = session.Alias("a").
Join("INNER", []string{tableName, "b"}, "a.owner = b.owner and a.name = b.name").
Select("b.*").
Desc("a." + util.SnakeString(sortField))
}
}
return session
}

View File

@ -55,6 +55,7 @@ type Payment struct {
// Order Info // Order Info
OutOrderId string `xorm:"varchar(100)" json:"outOrderId"` OutOrderId string `xorm:"varchar(100)" json:"outOrderId"`
PayUrl string `xorm:"varchar(2000)" json:"payUrl"` PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
SuccessUrl string `xorm:"varchar(2000)" json:"successUrl""` // `successUrl` is redirected from `payUrl` after pay success
State pp.PaymentState `xorm:"varchar(100)" json:"state"` State pp.PaymentState `xorm:"varchar(100)" json:"state"`
Message string `xorm:"varchar(2000)" json:"message"` Message string `xorm:"varchar(2000)" json:"message"`
} }
@ -152,7 +153,7 @@ func DeletePayment(payment *Payment) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func notifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, *pp.NotifyResult, error) { func notifyPayment(request *http.Request, body []byte, owner string, paymentName string) (*Payment, *pp.NotifyResult, error) {
payment, err := getPayment(owner, paymentName) payment, err := getPayment(owner, paymentName)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -180,11 +181,7 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
return nil, nil, err return nil, nil, err
} }
if orderId == "" { notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, payment.OutOrderId)
orderId = payment.OutOrderId
}
notifyResult, err := pProvider.Notify(request, body, cert.AuthorityPublicKey, orderId)
if err != nil { if err != nil {
return payment, nil, err return payment, nil, err
} }
@ -205,8 +202,8 @@ func notifyPayment(request *http.Request, body []byte, owner string, paymentName
return payment, notifyResult, nil return payment, notifyResult, nil
} }
func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string, orderId string) (*Payment, error) { func NotifyPayment(request *http.Request, body []byte, owner string, paymentName string) (*Payment, error) {
payment, notifyResult, err := notifyPayment(request, body, owner, paymentName, orderId) payment, notifyResult, err := notifyPayment(request, body, owner, paymentName)
if payment != nil { if payment != nil {
if err != nil { if err != nil {
payment.State = pp.PaymentStateError payment.State = pp.PaymentStateError

View File

@ -30,6 +30,7 @@ type Permission struct {
Description string `xorm:"varchar(100)" json:"description"` Description string `xorm:"varchar(100)" json:"description"`
Users []string `xorm:"mediumtext" json:"users"` Users []string `xorm:"mediumtext" json:"users"`
Groups []string `xorm:"mediumtext" json:"groups"`
Roles []string `xorm:"mediumtext" json:"roles"` Roles []string `xorm:"mediumtext" json:"roles"`
Domains []string `xorm:"mediumtext" json:"domains"` Domains []string `xorm:"mediumtext" json:"domains"`

View File

@ -79,9 +79,7 @@ func (p *Permission) setEnforcerAdapter(enforcer *casbin.Enforcer) error {
} }
} }
tableNamePrefix := conf.GetConfigString("tableNamePrefix") tableNamePrefix := conf.GetConfigString("tableNamePrefix")
driverName := conf.GetConfigString("driverName") adapter, err := xormadapter.NewAdapterByEngineWithTableName(ormer.Engine, tableName, tableNamePrefix)
dataSourceName := conf.GetConfigRealDataSourceName(driverName)
adapter, err := xormadapter.NewAdapterWithTableName(driverName, dataSourceName, tableName, tableNamePrefix, true)
if err != nil { if err != nil {
return err return err
} }

View File

@ -33,8 +33,8 @@ func getPermissionMap(owner string) (map[string]*Permission, error) {
return m, err return m, err
} }
func UploadPermissions(owner string, fileId string) (bool, error) { func UploadPermissions(owner string, path string) (bool, error) {
table := xlsx.ReadXlsxFile(fileId) table := xlsx.ReadXlsxFile(path)
oldUserMap, err := getPermissionMap(owner) oldUserMap, err := getPermissionMap(owner)
if err != nil { if err != nil {

View File

@ -16,6 +16,7 @@ package object
import ( import (
"fmt" "fmt"
"time"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/xorm-io/core" "github.com/xorm-io/core"
@ -28,15 +29,39 @@ type Plan struct {
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
Description string `xorm:"varchar(100)" json:"description"` Description string `xorm:"varchar(100)" json:"description"`
PricePerMonth float64 `json:"pricePerMonth"` Price float64 `json:"price"`
PricePerYear float64 `json:"pricePerYear"` Currency string `xorm:"varchar(100)" json:"currency"`
Currency string `xorm:"varchar(100)" json:"currency"` Period string `xorm:"varchar(100)" json:"period"`
IsEnabled bool `json:"isEnabled"` Product string `xorm:"varchar(100)" json:"product"`
PaymentProviders []string `xorm:"varchar(100)" json:"paymentProviders"` // payment providers for related product
IsEnabled bool `json:"isEnabled"`
Role string `xorm:"varchar(100)" json:"role"` Role string `xorm:"varchar(100)" json:"role"`
Options []string `xorm:"-" json:"options"` Options []string `xorm:"-" json:"options"`
} }
const (
PeriodMonthly = "Monthly"
PeriodYearly = "Yearly"
)
func (plan *Plan) GetId() string {
return fmt.Sprintf("%s/%s", plan.Owner, plan.Name)
}
func GetDuration(period string) (startTime time.Time, endTime time.Time) {
if period == PeriodYearly {
startTime = time.Now()
endTime = startTime.AddDate(1, 0, 0)
} else if period == PeriodMonthly {
startTime = time.Now()
endTime = startTime.AddDate(0, 1, 0)
} else {
panic(fmt.Sprintf("invalid period: %s", period))
}
return
}
func GetPlanCount(owner, field, value string) (int64, error) { func GetPlanCount(owner, field, value string) (int64, error) {
session := GetSession(owner, -1, -1, field, value, "", "") session := GetSession(owner, -1, -1, field, value, "", "")
return session.Count(&Plan{}) return session.Count(&Plan{})
@ -114,38 +139,3 @@ func DeletePlan(plan *Plan) (bool, error) {
} }
return affected != 0, nil return affected != 0, nil
} }
func (plan *Plan) GetId() string {
return fmt.Sprintf("%s/%s", plan.Owner, plan.Name)
}
func Subscribe(owner string, user string, plan string, pricing string) (*Subscription, error) {
selectedPricing, err := GetPricing(fmt.Sprintf("%s/%s", owner, pricing))
if err != nil {
return nil, err
}
valid := selectedPricing != nil && selectedPricing.IsEnabled
if !valid {
return nil, nil
}
planBelongToPricing, err := selectedPricing.HasPlan(owner, plan)
if err != nil {
return nil, err
}
if planBelongToPricing {
newSubscription := NewSubscription(owner, user, plan, selectedPricing.TrialDuration)
affected, err := AddSubscription(newSubscription)
if err != nil {
return nil, err
}
if affected {
return newSubscription, nil
}
}
return nil, nil
}

View File

@ -16,7 +16,6 @@ package object
import ( import (
"fmt" "fmt"
"strings"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/xorm-io/core" "github.com/xorm-io/core"
@ -33,12 +32,26 @@ type Pricing struct {
IsEnabled bool `json:"isEnabled"` IsEnabled bool `json:"isEnabled"`
TrialDuration int `json:"trialDuration"` TrialDuration int `json:"trialDuration"`
Application string `xorm:"varchar(100)" json:"application"` Application string `xorm:"varchar(100)" json:"application"`
}
Submitter string `xorm:"varchar(100)" json:"submitter"` func (pricing *Pricing) GetId() string {
Approver string `xorm:"varchar(100)" json:"approver"` return fmt.Sprintf("%s/%s", pricing.Owner, pricing.Name)
ApproveTime string `xorm:"varchar(100)" json:"approveTime"` }
State string `xorm:"varchar(100)" json:"state"` func (pricing *Pricing) HasPlan(planName string) (bool, error) {
planId := util.GetId(pricing.Owner, planName)
plan, err := GetPlan(planId)
if err != nil {
return false, err
}
if plan == nil {
return false, fmt.Errorf("plan: %s does not exist", planId)
}
if util.InSlice(pricing.Plans, plan.Name) {
return true, nil
}
return false, nil
} }
func GetPricingCount(owner, field, value string) (int64, error) { func GetPricingCount(owner, field, value string) (int64, error) {
@ -74,7 +87,7 @@ func getPricing(owner, name string) (*Pricing, error) {
pricing := Pricing{Owner: owner, Name: name} pricing := Pricing{Owner: owner, Name: name}
existed, err := ormer.Engine.Get(&pricing) existed, err := ormer.Engine.Get(&pricing)
if err != nil { if err != nil {
return &pricing, err return nil, err
} }
if existed { if existed {
return &pricing, nil return &pricing, nil
@ -88,6 +101,20 @@ func GetPricing(id string) (*Pricing, error) {
return getPricing(owner, name) return getPricing(owner, name)
} }
func GetApplicationDefaultPricing(owner, appName string) (*Pricing, error) {
pricings := make([]*Pricing, 0, 1)
err := ormer.Engine.Asc("created_time").Find(&pricings, &Pricing{Owner: owner, Application: appName})
if err != nil {
return nil, err
}
for _, pricing := range pricings {
if pricing.IsEnabled {
return pricing, nil
}
}
return nil, nil
}
func UpdatePricing(id string, pricing *Pricing) (bool, error) { func UpdatePricing(id string, pricing *Pricing) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id) owner, name := util.GetOwnerAndNameFromId(id)
if p, err := getPricing(owner, name); err != nil { if p, err := getPricing(owner, name); err != nil {
@ -120,28 +147,21 @@ func DeletePricing(pricing *Pricing) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func (pricing *Pricing) GetId() string { func CheckPricingAndPlan(owner, pricingName, planName string) error {
return fmt.Sprintf("%s/%s", pricing.Owner, pricing.Name) pricingId := util.GetId(owner, pricingName)
} pricing, err := GetPricing(pricingId)
if pricing == nil || err != nil {
func (pricing *Pricing) HasPlan(owner string, plan string) (bool, error) { if pricing == nil && err == nil {
selectedPlan, err := GetPlan(fmt.Sprintf("%s/%s", owner, plan)) err = fmt.Errorf("pricing: %s does not exist", pricingName)
if err != nil {
return false, err
}
if selectedPlan == nil {
return false, nil
}
result := false
for _, pricingPlan := range pricing.Plans {
if strings.Contains(pricingPlan, selectedPlan.Name) {
result = true
break
} }
return err
} }
ok, err := pricing.HasPlan(planName)
return result, nil if err != nil {
return err
}
if !ok {
return fmt.Errorf("pricing: %s does not have plan: %s", pricingName, planName)
}
return nil
} }

View File

@ -37,7 +37,7 @@ type Product struct {
Price float64 `json:"price"` Price float64 `json:"price"`
Quantity int `json:"quantity"` Quantity int `json:"quantity"`
Sold int `json:"sold"` Sold int `json:"sold"`
Providers []string `xorm:"varchar(100)" json:"providers"` Providers []string `xorm:"varchar(255)" json:"providers"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"` ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"` State string `xorm:"varchar(100)" json:"state"`
@ -141,59 +141,76 @@ func (product *Product) isValidProvider(provider *Provider) bool {
return false return false
} }
func (product *Product) getProvider(providerId string) (*Provider, error) { func (product *Product) getProvider(providerName string) (*Provider, error) {
provider, err := getProvider(product.Owner, providerId) provider, err := getProvider(product.Owner, providerName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if provider == nil { if provider == nil {
return nil, fmt.Errorf("the payment provider: %s does not exist", providerId) return nil, fmt.Errorf("the payment provider: %s does not exist", providerName)
} }
if !product.isValidProvider(provider) { if !product.isValidProvider(provider) {
return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerId, product.Name) return nil, fmt.Errorf("the payment provider: %s is not valid for the product: %s", providerName, product.Name)
} }
return provider, nil return provider, nil
} }
func BuyProduct(id string, providerName string, user *User, host string) (string, string, error) { func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (*Payment, error) {
product, err := GetProduct(id) product, err := GetProduct(id)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
if product == nil { if product == nil {
return "", "", fmt.Errorf("the product: %s does not exist", id) return nil, fmt.Errorf("the product: %s does not exist", id)
} }
provider, err := product.getProvider(providerName) provider, err := product.getProvider(providerName)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
pProvider, _, err := provider.getPaymentProvider() pProvider, _, err := provider.getPaymentProvider()
if err != nil { if err != nil {
return "", "", err return nil, err
} }
owner := product.Owner owner := product.Owner
productName := product.Name productName := product.Name
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName) payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
paymentName := util.GenerateTimeId() paymentName := fmt.Sprintf("payment_%v", util.GenerateTimeId())
productDisplayName := product.DisplayName productDisplayName := product.DisplayName
originFrontend, originBackend := getOriginFromHost(host) originFrontend, originBackend := getOriginFromHost(host)
returnUrl := fmt.Sprintf("%s/payments/%s/%s/result", originFrontend, owner, paymentName) returnUrl := fmt.Sprintf("%s/payments/%s/%s/result", originFrontend, owner, paymentName)
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s", originBackend, owner, paymentName) notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s", originBackend, owner, paymentName)
// Create an Order and get the payUrl if user.Type == "paid-user" {
// Create a subscription for `paid-user`
if pricingName != "" && planName != "" {
plan, err := GetPlan(util.GetId(owner, planName))
if err != nil {
return nil, err
}
if plan == nil {
return nil, fmt.Errorf("the plan: %s does not exist", planName)
}
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
_, err = AddSubscription(sub)
if err != nil {
return nil, err
}
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
}
}
// Create an OrderId and get the payUrl
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl) payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
// Create a Payment linked with Product and Order // Create a Payment linked with Product and Order
payment := Payment{ payment := &Payment{
Owner: product.Owner, Owner: product.Owner,
Name: paymentName, Name: paymentName,
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
@ -212,6 +229,7 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
User: user.Name, User: user.Name,
PayUrl: payUrl, PayUrl: payUrl,
SuccessUrl: returnUrl,
State: pp.PaymentStateCreated, State: pp.PaymentStateCreated,
OutOrderId: orderId, OutOrderId: orderId,
} }
@ -220,16 +238,15 @@ func BuyProduct(id string, providerName string, user *User, host string) (string
payment.State = pp.PaymentStatePaid payment.State = pp.PaymentStatePaid
} }
affected, err := AddPayment(&payment) affected, err := AddPayment(payment)
if err != nil { if err != nil {
return "", "", err return nil, err
} }
if !affected { if !affected {
return "", "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment)) return nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
} }
return payment, err
return payUrl, orderId, err
} }
func ExtendProductWithProviders(product *Product) error { func ExtendProductWithProviders(product *Product) error {
@ -252,3 +269,38 @@ func ExtendProductWithProviders(product *Product) error {
return nil return nil
} }
func CreateProductForPlan(plan *Plan) *Product {
product := &Product{
Owner: plan.Owner,
Name: fmt.Sprintf("product_%v", util.GetRandomName()),
DisplayName: fmt.Sprintf("Product for Plan %v/%v/%v", plan.Name, plan.DisplayName, plan.Period),
CreatedTime: plan.CreatedTime,
Image: "https://cdn.casbin.org/img/casdoor-logo_1185x256.png", // TODO
Detail: fmt.Sprintf("This product was auto created for plan %v(%v), subscription period is %v", plan.Name, plan.DisplayName, plan.Period),
Description: plan.Description,
Tag: "auto_created_product_for_plan",
Price: plan.Price,
Currency: plan.Currency,
Quantity: 999,
Sold: 0,
Providers: plan.PaymentProviders,
State: "Published",
}
if product.Providers == nil {
product.Providers = []string{}
}
return product
}
func UpdateProductForPlan(plan *Plan, product *Product) {
product.Owner = plan.Owner
product.DisplayName = fmt.Sprintf("Product for Plan %v/%v/%v", plan.Name, plan.DisplayName, plan.Period)
product.Detail = fmt.Sprintf("This product was auto created for plan %v(%v), subscription period is %v", plan.Name, plan.DisplayName, plan.Period)
product.Price = plan.Price
product.Currency = plan.Currency
product.Providers = plan.PaymentProviders
}

View File

@ -21,6 +21,7 @@ import (
"github.com/beego/beego/context" "github.com/beego/beego/context"
"github.com/casdoor/casdoor/conf" "github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/casvisor/casvisor-go-sdk/casvisorsdk"
) )
var logPostOnly bool var logPostOnly bool
@ -30,26 +31,10 @@ func init() {
} }
type Record struct { type Record struct {
Id int `xorm:"int notnull pk autoincr" json:"id"` casvisorsdk.Record
Owner string `xorm:"varchar(100) index" json:"owner"`
Name string `xorm:"varchar(100) index" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Organization string `xorm:"varchar(100)" json:"organization"`
ClientIp string `xorm:"varchar(100)" json:"clientIp"`
User string `xorm:"varchar(100)" json:"user"`
Method string `xorm:"varchar(100)" json:"method"`
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
Action string `xorm:"varchar(1000)" json:"action"`
Object string `xorm:"-" json:"object"`
ExtendedUser *User `xorm:"-" json:"extendedUser"`
IsTriggered bool `json:"isTriggered"`
} }
func NewRecord(ctx *context.Context) *Record { func NewRecord(ctx *context.Context) *casvisorsdk.Record {
ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1) ip := strings.Replace(util.GetIPFromRequest(ctx.Request), ": ", "", -1)
action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1) action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1)
requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"}) requestUri := util.FilterQuery(ctx.Request.RequestURI, []string{"accessToken"})
@ -62,7 +47,7 @@ func NewRecord(ctx *context.Context) *Record {
object = string(ctx.Input.RequestBody) object = string(ctx.Input.RequestBody)
} }
record := Record{ record := casvisorsdk.Record{
Name: util.GenerateId(), Name: util.GenerateId(),
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
ClientIp: ip, ClientIp: ip,
@ -76,7 +61,7 @@ func NewRecord(ctx *context.Context) *Record {
return &record return &record
} }
func AddRecord(record *Record) bool { func AddRecord(record *casvisorsdk.Record) bool {
if logPostOnly { if logPostOnly {
if record.Method == "GET" { if record.Method == "GET" {
return false return false
@ -96,51 +81,19 @@ func AddRecord(record *Record) bool {
fmt.Println(errWebhook) fmt.Println(errWebhook)
} }
affected, err := ormer.Engine.Insert(record) if casvisorsdk.GetClient() == nil {
return false
}
affected, err := casvisorsdk.AddRecord(record)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return affected != 0 return affected
} }
func GetRecordCount(field, value string, filterRecord *Record) (int64, error) { func SendWebhooks(record *casvisorsdk.Record) error {
session := GetSession("", -1, -1, field, value, "", "")
return session.Count(filterRecord)
}
func GetRecords() ([]*Record, error) {
records := []*Record{}
err := ormer.Engine.Desc("id").Find(&records)
if err != nil {
return records, err
}
return records, nil
}
func GetPaginationRecords(offset, limit int, field, value, sortField, sortOrder string, filterRecord *Record) ([]*Record, error) {
records := []*Record{}
session := GetSession("", offset, limit, field, value, sortField, sortOrder)
err := session.Find(&records, filterRecord)
if err != nil {
return records, err
}
return records, nil
}
func GetRecordsByField(record *Record) ([]*Record, error) {
records := []*Record{}
err := ormer.Engine.Find(&records, record)
if err != nil {
return records, err
}
return records, nil
}
func SendWebhooks(record *Record) error {
webhooks, err := getWebhooksByOrganization(record.Organization) webhooks, err := getWebhooksByOrganization(record.Organization)
if err != nil { if err != nil {
return err return err
@ -160,17 +113,16 @@ func SendWebhooks(record *Record) error {
} }
if matched { if matched {
var user *User
if webhook.IsUserExtended { if webhook.IsUserExtended {
user, err := getUser(record.Organization, record.User) user, err = getUser(record.Organization, record.User)
user, err = GetMaskedUser(user, false, err) user, err = GetMaskedUser(user, false, err)
if err != nil { if err != nil {
return err return err
} }
record.ExtendedUser = user
} }
err := sendWebhook(webhook, record) err = sendWebhook(webhook, record, user)
if err != nil { if err != nil {
return err return err
} }

50
object/record_casvisor.go Normal file
View File

@ -0,0 +1,50 @@
// Copyright 2023 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"strings"
"github.com/casvisor/casvisor-go-sdk/casvisorsdk"
)
func getCasvisorApplication() *Application {
applications, err := GetApplications("admin")
if err != nil {
panic(err)
}
for _, application := range applications {
if strings.Contains(strings.ToLower(application.Name), "casvisor") {
return application
}
}
return nil
}
func InitCasvisorConfig() {
application := getCasvisorApplication()
if application == nil {
return
}
casvisorEndpoint := application.HomepageUrl
clientId := application.ClientId
clientSecret := application.ClientSecret
casdoorOrganization := application.Organization
casdoorApplication := application.Name
casvisorsdk.InitConfig(casvisorEndpoint, clientId, clientSecret, casdoorOrganization, casdoorApplication)
}

View File

@ -33,8 +33,8 @@ func getRoleMap(owner string) (map[string]*Role, error) {
return m, nil return m, nil
} }
func UploadRoles(owner string, fileId string) (bool, error) { func UploadRoles(owner string, path string) (bool, error) {
table := xlsx.ReadXlsxFile(fileId) table := xlsx.ReadXlsxFile(path)
oldUserMap, err := getRoleMap(owner) oldUserMap, err := getRoleMap(owner)
if err != nil { if err != nil {

View File

@ -31,8 +31,8 @@ import (
"github.com/RobotsAndPencils/go-saml" "github.com/RobotsAndPencils/go-saml"
"github.com/beevik/etree" "github.com/beevik/etree"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
dsig "github.com/russellhaering/goxmldsig" dsig "github.com/russellhaering/goxmldsig"
uuid "github.com/satori/go.uuid"
) )
// NewSamlResponse // NewSamlResponse
@ -46,7 +46,7 @@ func NewSamlResponse(user *User, host string, certificate string, destination st
expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339) expireTime := time.Now().UTC().Add(time.Hour * 24).Format(time.RFC3339)
samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol") samlResponse.CreateAttr("xmlns:samlp", "urn:oasis:names:tc:SAML:2.0:protocol")
samlResponse.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion") samlResponse.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:2.0:assertion")
arId := uuid.NewV4() arId := uuid.New()
samlResponse.CreateAttr("ID", fmt.Sprintf("_%s", arId)) samlResponse.CreateAttr("ID", fmt.Sprintf("_%s", arId))
samlResponse.CreateAttr("Version", "2.0") samlResponse.CreateAttr("Version", "2.0")
@ -60,7 +60,7 @@ func NewSamlResponse(user *User, host string, certificate string, destination st
assertion := samlResponse.CreateElement("saml:Assertion") assertion := samlResponse.CreateElement("saml:Assertion")
assertion.CreateAttr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance") assertion.CreateAttr("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
assertion.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema") assertion.CreateAttr("xmlns:xs", "http://www.w3.org/2001/XMLSchema")
assertion.CreateAttr("ID", fmt.Sprintf("_%s", uuid.NewV4())) assertion.CreateAttr("ID", fmt.Sprintf("_%s", uuid.New()))
assertion.CreateAttr("Version", "2.0") assertion.CreateAttr("Version", "2.0")
assertion.CreateAttr("IssueInstant", now) assertion.CreateAttr("IssueInstant", now)
assertion.CreateElement("saml:Issuer").SetText(host) assertion.CreateElement("saml:Issuer").SetText(host)
@ -82,7 +82,7 @@ func NewSamlResponse(user *User, host string, certificate string, destination st
} }
authnStatement := assertion.CreateElement("saml:AuthnStatement") authnStatement := assertion.CreateElement("saml:AuthnStatement")
authnStatement.CreateAttr("AuthnInstant", now) authnStatement.CreateAttr("AuthnInstant", now)
authnStatement.CreateAttr("SessionIndex", fmt.Sprintf("_%s", uuid.NewV4())) authnStatement.CreateAttr("SessionIndex", fmt.Sprintf("_%s", uuid.New()))
authnStatement.CreateAttr("SessionNotOnOrAfter", expireTime) authnStatement.CreateAttr("SessionNotOnOrAfter", expireTime)
authnStatement.CreateElement("saml:AuthnContext").CreateElement("saml:AuthnContextClassRef").SetText("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport") authnStatement.CreateElement("saml:AuthnContext").CreateElement("saml:AuthnContextClassRef").SetText("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport")
@ -355,7 +355,7 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
samlResponse.CreateAttr("MajorVersion", "1") samlResponse.CreateAttr("MajorVersion", "1")
samlResponse.CreateAttr("MinorVersion", "1") samlResponse.CreateAttr("MinorVersion", "1")
responseID := uuid.NewV4() responseID := uuid.New()
samlResponse.CreateAttr("ResponseID", fmt.Sprintf("_%s", responseID)) samlResponse.CreateAttr("ResponseID", fmt.Sprintf("_%s", responseID))
samlResponse.CreateAttr("InResponseTo", requestID) samlResponse.CreateAttr("InResponseTo", requestID)
@ -371,7 +371,7 @@ func NewSamlResponse11(user *User, requestID string, host string) *etree.Element
assertion.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:1.0:assertion") assertion.CreateAttr("xmlns:saml", "urn:oasis:names:tc:SAML:1.0:assertion")
assertion.CreateAttr("MajorVersion", "1") assertion.CreateAttr("MajorVersion", "1")
assertion.CreateAttr("MinorVersion", "1") assertion.CreateAttr("MinorVersion", "1")
assertion.CreateAttr("AssertionID", uuid.NewV4().String()) assertion.CreateAttr("AssertionID", uuid.New().String())
assertion.CreateAttr("Issuer", host) assertion.CreateAttr("Issuer", host)
assertion.CreateAttr("IssueInstant", now) assertion.CreateAttr("IssueInstant", now)

View File

@ -26,8 +26,6 @@ func getSmsClient(provider *Provider) (sender.SmsClient, error) {
if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS { if provider.Type == sender.HuaweiCloud || provider.Type == sender.AzureACS {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId) client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
} else if provider.Type == "Custom HTTP SMS" {
client, err = newHttpSmsClient(provider.Endpoint, provider.Method, provider.ClientId, provider.Title)
} else { } else {
client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId) client, err = sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
} }

View File

@ -18,47 +18,108 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/xorm-io/core" "github.com/xorm-io/core"
) )
const defaultStatus = "Pending" type SubscriptionState string
const (
SubStatePending SubscriptionState = "Pending"
SubStateError SubscriptionState = "Error"
SubStateSuspended SubscriptionState = "Suspended" // suspended by the admin
SubStateActive SubscriptionState = "Active"
SubStateUpcoming SubscriptionState = "Upcoming"
SubStateExpired SubscriptionState = "Expired"
)
type Subscription struct { type Subscription struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"` Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"` Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"` DisplayName string `xorm:"varchar(100)" json:"displayName"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Description string `xorm:"varchar(100)" json:"description"`
StartDate time.Time `json:"startDate"` User string `xorm:"varchar(100)" json:"user"`
EndDate time.Time `json:"endDate"` Pricing string `xorm:"varchar(100)" json:"pricing"`
Duration int `json:"duration"` Plan string `xorm:"varchar(100)" json:"plan"`
Description string `xorm:"varchar(100)" json:"description"` Payment string `xorm:"varchar(100)" json:"payment"`
User string `xorm:"mediumtext" json:"user"` StartTime time.Time `json:"startTime"`
Plan string `xorm:"varchar(100)" json:"plan"` EndTime time.Time `json:"endTime"`
Period string `xorm:"varchar(100)" json:"period"`
IsEnabled bool `json:"isEnabled"` State SubscriptionState `xorm:"varchar(100)" json:"state"`
Submitter string `xorm:"varchar(100)" json:"submitter"`
Approver string `xorm:"varchar(100)" json:"approver"`
ApproveTime string `xorm:"varchar(100)" json:"approveTime"`
State string `xorm:"varchar(100)" json:"state"`
} }
func NewSubscription(owner string, user string, plan string, duration int) *Subscription { func (sub *Subscription) GetId() string {
return fmt.Sprintf("%s/%s", sub.Owner, sub.Name)
}
func (sub *Subscription) UpdateState() error {
preState := sub.State
// update subscription state by payment state
if sub.State == SubStatePending {
if sub.Payment == "" {
return nil
}
payment, err := GetPayment(util.GetId(sub.Owner, sub.Payment))
if err != nil {
return err
}
if payment == nil {
sub.Description = fmt.Sprintf("payment: %s does not exist", sub.Payment)
sub.State = SubStateError
} else {
if payment.State == pp.PaymentStatePaid {
sub.State = SubStateActive
} else if payment.State != pp.PaymentStateCreated {
// other states: Canceled, Timeout, Error
sub.Description = fmt.Sprintf("payment: %s state is %v", sub.Payment, payment.State)
sub.State = SubStateError
}
}
}
if sub.State == SubStateActive || sub.State == SubStateUpcoming || sub.State == SubStateExpired {
if sub.EndTime.Before(time.Now()) {
sub.State = SubStateExpired
} else if sub.StartTime.After(time.Now()) {
sub.State = SubStateUpcoming
} else {
sub.State = SubStateActive
}
}
if preState != sub.State {
_, err := UpdateSubscription(sub.GetId(), sub)
if err != nil {
return err
}
}
return nil
}
func NewSubscription(owner, userName, planName, paymentName, period string) *Subscription {
startTime, endTime := GetDuration(period)
id := util.GenerateId()[:6] id := util.GenerateId()[:6]
return &Subscription{ return &Subscription{
Name: "Subscription_" + id,
DisplayName: "New Subscription - " + id,
Owner: owner, Owner: owner,
User: owner + "/" + user, Name: "sub_" + id,
Plan: owner + "/" + plan, DisplayName: "New Subscription - " + id,
CreatedTime: util.GetCurrentTime(), CreatedTime: util.GetCurrentTime(),
State: defaultStatus,
Duration: duration, User: userName,
StartDate: time.Now(), Plan: planName,
EndDate: time.Now().AddDate(0, 0, duration), Payment: paymentName,
StartTime: startTime,
EndTime: endTime,
Period: period,
State: SubStatePending, // waiting for payment complete
} }
} }
@ -73,7 +134,28 @@ func GetSubscriptions(owner string) ([]*Subscription, error) {
if err != nil { if err != nil {
return subscriptions, err return subscriptions, err
} }
for _, sub := range subscriptions {
err = sub.UpdateState()
if err != nil {
return nil, err
}
}
return subscriptions, nil
}
func GetSubscriptionsByUser(owner, userName string) ([]*Subscription, error) {
subscriptions := []*Subscription{}
err := ormer.Engine.Desc("created_time").Find(&subscriptions, &Subscription{Owner: owner, User: userName})
if err != nil {
return subscriptions, err
}
// update subscription state
for _, sub := range subscriptions {
err = sub.UpdateState()
if err != nil {
return subscriptions, err
}
}
return subscriptions, nil return subscriptions, nil
} }
@ -84,7 +166,12 @@ func GetPaginationSubscriptions(owner string, offset, limit int, field, value, s
if err != nil { if err != nil {
return subscriptions, err return subscriptions, err
} }
for _, sub := range subscriptions {
err = sub.UpdateState()
if err != nil {
return nil, err
}
}
return subscriptions, nil return subscriptions, nil
} }
@ -144,7 +231,3 @@ func DeleteSubscription(subscription *Subscription) (bool, error) {
return affected != 0, nil return affected != 0, nil
} }
func (subscription *Subscription) GetId() string {
return fmt.Sprintf("%s/%s", subscription.Owner, subscription.Name)
}

View File

@ -37,12 +37,13 @@ type Syncer struct {
Organization string `xorm:"varchar(100)" json:"organization"` Organization string `xorm:"varchar(100)" json:"organization"`
Type string `xorm:"varchar(100)" json:"type"` Type string `xorm:"varchar(100)" json:"type"`
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
SslMode string `xorm:"varchar(100)" json:"sslMode"`
Host string `xorm:"varchar(100)" json:"host"` Host string `xorm:"varchar(100)" json:"host"`
Port int `json:"port"` Port int `json:"port"`
User string `xorm:"varchar(100)" json:"user"` User string `xorm:"varchar(100)" json:"user"`
Password string `xorm:"varchar(100)" json:"password"` Password string `xorm:"varchar(100)" json:"password"`
DatabaseType string `xorm:"varchar(100)" json:"databaseType"`
Database string `xorm:"varchar(100)" json:"database"` Database string `xorm:"varchar(100)" json:"database"`
Table string `xorm:"varchar(100)" json:"table"` Table string `xorm:"varchar(100)" json:"table"`
TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"` TableColumns []*TableColumn `xorm:"mediumtext" json:"tableColumns"`
@ -250,7 +251,7 @@ func (syncer *Syncer) getKey() string {
return key return key
} }
func RunSyncer(syncer *Syncer) { func RunSyncer(syncer *Syncer) error {
syncer.initAdapter() syncer.initAdapter()
syncer.syncUsers() return syncer.syncUsers()
} }

View File

@ -52,11 +52,14 @@ func addSyncerJob(syncer *Syncer) error {
syncer.initAdapter() syncer.initAdapter()
syncer.syncUsers() err := syncer.syncUsers()
if err != nil {
return err
}
schedule := fmt.Sprintf("@every %ds", syncer.SyncInterval) schedule := fmt.Sprintf("@every %ds", syncer.SyncInterval)
cron := getCronMap(syncer.Name) cron := getCronMap(syncer.Name)
_, err := cron.AddFunc(schedule, syncer.syncUsers) _, err = cron.AddFunc(schedule, syncer.syncUsersNoError)
if err != nil { if err != nil {
return err return err
} }

View File

@ -19,15 +19,15 @@ import (
"time" "time"
) )
func (syncer *Syncer) syncUsers() { func (syncer *Syncer) syncUsers() error {
if len(syncer.TableColumns) == 0 { if len(syncer.TableColumns) == 0 {
return return fmt.Errorf("The syncer table columns should not be empty")
} }
fmt.Printf("Running syncUsers()..\n") fmt.Printf("Running syncUsers()..\n")
users, _, _ := syncer.getUserMap() users, _, _ := syncer.getUserMap()
oUsers, oUserMap, err := syncer.getOriginalUserMap() oUsers, _, err := syncer.getOriginalUserMap()
if err != nil { if err != nil {
fmt.Printf(err.Error()) fmt.Printf(err.Error())
@ -35,10 +35,8 @@ func (syncer *Syncer) syncUsers() {
line := fmt.Sprintf("[%s] %s\n", timestamp, err.Error()) line := fmt.Sprintf("[%s] %s\n", timestamp, err.Error())
_, err = updateSyncerErrorText(syncer, line) _, err = updateSyncerErrorText(syncer, line)
if err != nil { if err != nil {
panic(err) return err
} }
return
} }
fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers)) fmt.Printf("Users: %d, oUsers: %d\n", len(users), len(oUsers))
@ -55,6 +53,11 @@ func (syncer *Syncer) syncUsers() {
myUsers[syncer.getUserValue(m, key)] = m myUsers[syncer.getUserValue(m, key)] = m
} }
myOUsers := map[string]*User{}
for _, m := range oUsers {
myOUsers[syncer.getUserValue(m, key)] = m
}
newUsers := []*User{} newUsers := []*User{}
for _, oUser := range oUsers { for _, oUser := range oUsers {
primary := syncer.getUserValue(oUser, key) primary := syncer.getUserValue(oUser, key)
@ -71,28 +74,30 @@ func (syncer *Syncer) syncUsers() {
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap) updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash updatedUser.Hash = oHash
updatedUser.PreHash = oHash updatedUser.PreHash = oHash
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
_, err = syncer.updateUserForOriginalByFields(updatedUser, key) _, err = syncer.updateUserForOriginalByFields(updatedUser, key)
if err != nil { if err != nil {
panic(err) return err
} }
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
} }
} else { } else {
if user.PreHash == oHash { if user.PreHash == oHash {
if !syncer.IsReadOnly { if !syncer.IsReadOnly {
updatedOUser := syncer.createOriginalUserFromUser(user) updatedOUser := syncer.createOriginalUserFromUser(user)
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
_, err = syncer.updateUser(updatedOUser) _, err = syncer.updateUser(updatedOUser)
if err != nil { if err != nil {
panic(err) return err
} }
fmt.Printf("Update from user to oUser: %v\n", updatedOUser)
} }
// update preHash // update preHash
user.PreHash = user.Hash user.PreHash = user.Hash
_, err = SetUserField(user, "pre_hash", user.PreHash) _, err = SetUserField(user, "pre_hash", user.PreHash)
if err != nil { if err != nil {
panic(err) return err
} }
} else { } else {
if user.Hash == oHash { if user.Hash == oHash {
@ -100,17 +105,18 @@ func (syncer *Syncer) syncUsers() {
user.PreHash = user.Hash user.PreHash = user.Hash
_, err = SetUserField(user, "pre_hash", user.PreHash) _, err = SetUserField(user, "pre_hash", user.PreHash)
if err != nil { if err != nil {
panic(err) return err
} }
} else { } else {
updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap) updatedUser := syncer.createUserFromOriginalUser(oUser, affiliationMap)
updatedUser.Hash = oHash updatedUser.Hash = oHash
updatedUser.PreHash = oHash updatedUser.PreHash = oHash
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
_, err = syncer.updateUserForOriginalByFields(updatedUser, key) _, err = syncer.updateUserForOriginalByFields(updatedUser, key)
if err != nil { if err != nil {
panic(err) return err
} }
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
} }
} }
} }
@ -118,20 +124,30 @@ func (syncer *Syncer) syncUsers() {
} }
_, err = AddUsersInBatch(newUsers) _, err = AddUsersInBatch(newUsers)
if err != nil { if err != nil {
panic(err) return err
} }
if !syncer.IsReadOnly { if !syncer.IsReadOnly {
for _, user := range users { for _, user := range users {
id := user.Id primary := syncer.getUserValue(user, key)
if _, ok := oUserMap[id]; !ok { if _, ok := myOUsers[primary]; !ok {
newOUser := syncer.createOriginalUserFromUser(user) newOUser := syncer.createOriginalUserFromUser(user)
fmt.Printf("New oUser: %v\n", newOUser)
_, err = syncer.addUser(newOUser) _, err = syncer.addUser(newOUser)
if err != nil { if err != nil {
panic(err) return err
} }
fmt.Printf("New oUser: %v\n", newOUser)
} }
} }
} }
return nil
}
func (syncer *Syncer) syncUsersNoError() {
err := syncer.syncUsers()
if err != nil {
fmt.Printf("syncUsersNoError() error: %s\n", err.Error())
}
} }

View File

@ -31,8 +31,8 @@ type Credential struct {
} }
func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) { func (syncer *Syncer) getOriginalUsers() ([]*OriginalUser, error) {
sql := fmt.Sprintf("select * from %s", syncer.getTable()) var results []map[string]string
results, err := syncer.Ormer.Engine.QueryString(sql) err := syncer.Ormer.Engine.Table(syncer.getTable()).Find(&results)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -64,19 +64,10 @@ func (syncer *Syncer) getOriginalUserMap() ([]*OriginalUser, map[string]*Origina
func (syncer *Syncer) addUser(user *OriginalUser) (bool, error) { func (syncer *Syncer) addUser(user *OriginalUser) (bool, error) {
m := syncer.getMapFromOriginalUser(user) m := syncer.getMapFromOriginalUser(user)
keyString, valueString := syncer.getSqlKeyValueStringFromMap(m) affected, err := syncer.Ormer.Engine.Table(syncer.getTable()).Insert(m)
sql := fmt.Sprintf("insert into %s (%s) values (%s)", syncer.getTable(), keyString, valueString)
res, err := syncer.Ormer.Engine.Exec(sql)
if err != nil { if err != nil {
return false, err return false, err
} }
affected, err := res.RowsAffected()
if err != nil {
return false, err
}
return affected != 0, nil return affected != 0, nil
} }
@ -93,23 +84,14 @@ func (syncer *Syncer) getCasdoorColumns() []string {
func (syncer *Syncer) updateUser(user *OriginalUser) (bool, error) { func (syncer *Syncer) updateUser(user *OriginalUser) (bool, error) {
key := syncer.getKey() key := syncer.getKey()
m := syncer.getMapFromOriginalUser(user) m := syncer.getMapFromOriginalUser(user)
pkValue := m[key] pkValue := m[key]
delete(m, key) delete(m, key)
setString := syncer.getSqlSetStringFromMap(m)
sql := fmt.Sprintf("update %s set %s where %s = %s", syncer.getTable(), setString, key, pkValue) affected, err := syncer.Ormer.Engine.Table(syncer.getTable()).ID(pkValue).Update(&m)
res, err := syncer.Ormer.Engine.Exec(sql)
if err != nil { if err != nil {
return false, err return false, err
} }
affected, err := res.RowsAffected()
if err != nil {
return false, err
}
return affected != 0, nil return affected != 0, nil
} }
@ -185,7 +167,11 @@ func (syncer *Syncer) initAdapter() {
if syncer.DatabaseType == "mssql" { if syncer.DatabaseType == "mssql" {
dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database) dataSourceName = fmt.Sprintf("sqlserver://%s:%s@%s:%d?database=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database)
} else if syncer.DatabaseType == "postgres" { } else if syncer.DatabaseType == "postgres" {
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=disable dbname=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, syncer.Database) sslMode := "disable"
if syncer.SslMode != "" {
sslMode = syncer.SslMode
}
dataSourceName = fmt.Sprintf("user=%s password=%s host=%s port=%d sslmode=%s dbname=%s", syncer.User, syncer.Password, syncer.Host, syncer.Port, sslMode, syncer.Database)
} else { } else {
dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port) dataSourceName = fmt.Sprintf("%s:%s@tcp(%s:%d)/", syncer.User, syncer.Password, syncer.Host, syncer.Port)
} }

View File

@ -160,6 +160,12 @@ func (syncer *Syncer) setUserByKeyValue(user *User, key string, value string) {
user.IsDeleted = util.ParseBool(value) user.IsDeleted = util.ParseBool(value)
case "CreatedIp": case "CreatedIp":
user.CreatedIp = value user.CreatedIp = value
case "PreferredMfaType":
user.PreferredMfaType = value
case "TotpSecret":
user.TotpSecret = value
case "SignupApplication":
user.SignupApplication = value
} }
} }
@ -290,6 +296,9 @@ func (syncer *Syncer) getMapFromOriginalUser(user *OriginalUser) map[string]stri
m["IsForbidden"] = util.BoolToString(user.IsForbidden) m["IsForbidden"] = util.BoolToString(user.IsForbidden)
m["IsDeleted"] = util.BoolToString(user.IsDeleted) m["IsDeleted"] = util.BoolToString(user.IsDeleted)
m["CreatedIp"] = user.CreatedIp m["CreatedIp"] = user.CreatedIp
m["PreferredMfaType"] = user.PreferredMfaType
m["TotpSecret"] = user.TotpSecret
m["SignupApplication"] = user.SignupApplication
m2 := map[string]string{} m2 := map[string]string{}
for _, tableColumn := range syncer.TableColumns { for _, tableColumn := range syncer.TableColumns {
@ -313,19 +322,3 @@ func (syncer *Syncer) getSqlSetStringFromMap(m map[string]string) string {
} }
return strings.Join(tokens, ", ") return strings.Join(tokens, ", ")
} }
func (syncer *Syncer) getSqlKeyValueStringFromMap(m map[string]string) (string, string) {
typeMap := syncer.getTableColumnsTypeMap()
keys := []string{}
values := []string{}
for k, v := range m {
if typeMap[k] == "string" {
v = fmt.Sprintf("'%s'", v)
}
keys = append(keys, k)
values = append(values, v)
}
return strings.Join(keys, ", "), strings.Join(values, ", ")
}

View File

@ -185,38 +185,52 @@ func StoreCasTokenForProxyTicket(token *CasAuthenticationSuccess, targetService,
} }
func GenerateCasToken(userId string, service string) (string, error) { func GenerateCasToken(userId string, service string) (string, error) {
if user, err := GetUser(userId); err != nil { user, err := GetUser(userId)
if err != nil {
return "", err return "", err
} else if user != nil {
authenticationSuccess := CasAuthenticationSuccess{
User: user.Name,
Attributes: &CasAttributes{
AuthenticationDate: time.Now(),
UserAttributes: &CasUserAttributes{},
},
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
}
data, _ := json.Marshal(user)
tmp := map[string]string{}
json.Unmarshal(data, &tmp)
for k, v := range tmp {
if v != "" {
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k,
Value: v,
})
}
}
st := fmt.Sprintf("ST-%d", rand.Int())
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: &authenticationSuccess,
Service: service,
UserId: userId,
})
return st, nil
} else {
return "", fmt.Errorf("invalid user Id")
} }
if user == nil {
return "", fmt.Errorf("The user: %s doesn't exist", userId)
}
user, _ = GetMaskedUser(user, false)
authenticationSuccess := CasAuthenticationSuccess{
User: user.Name,
Attributes: &CasAttributes{
AuthenticationDate: time.Now(),
UserAttributes: &CasUserAttributes{},
},
ProxyGrantingTicket: fmt.Sprintf("PGTIOU-%s", util.GenerateId()),
}
data, err := json.Marshal(user)
if err != nil {
return "", err
}
tmp := map[string]string{}
err = json.Unmarshal(data, &tmp)
if err != nil {
return "", err
}
for k, v := range tmp {
if v != "" {
authenticationSuccess.Attributes.UserAttributes.Attributes = append(authenticationSuccess.Attributes.UserAttributes.Attributes, &CasNamedAttribute{
Name: k,
Value: v,
})
}
}
st := fmt.Sprintf("ST-%d", rand.Int())
stToServiceResponse.Store(st, &CasAuthenticationSuccessWrapper{
AuthenticationSuccess: &authenticationSuccess,
Service: service,
UserId: userId,
})
return st, nil
} }
// GetValidationBySaml // GetValidationBySaml

View File

@ -194,16 +194,17 @@ type User struct {
} }
type Userinfo struct { type Userinfo struct {
Sub string `json:"sub"` Sub string `json:"sub"`
Iss string `json:"iss"` Iss string `json:"iss"`
Aud string `json:"aud"` Aud string `json:"aud"`
Name string `json:"preferred_username,omitempty"` Name string `json:"preferred_username,omitempty"`
DisplayName string `json:"name,omitempty"` DisplayName string `json:"name,omitempty"`
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
Avatar string `json:"picture,omitempty"` EmailVerified bool `json:"email,omitempty"`
Address string `json:"address,omitempty"` Avatar string `json:"picture,omitempty"`
Phone string `json:"phone,omitempty"` Address string `json:"address,omitempty"`
Groups []string `json:"groups,omitempty"` Phone string `json:"phone,omitempty"`
Groups []string `json:"groups,omitempty"`
} }
type ManagedAccount struct { type ManagedAccount struct {
@ -536,12 +537,12 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox", "auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
"eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup", "eveonline", "fitbit", "gitea", "heroku", "influxcloud", "instagram", "intercom", "kakao", "lastfm", "mailru", "meetup",
"microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud", "microsoftonline", "naver", "nextcloud", "onedrive", "oura", "patreon", "paypal", "salesforce", "shopify", "soundcloud",
"spotify", "strava", "stripe", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo", "spotify", "strava", "stripe", "type", "tiktok", "tumblr", "twitch", "twitter", "typetalk", "uber", "vk", "wepay", "xero", "yahoo",
"yammer", "yandex", "zoom", "custom", "yammer", "yandex", "zoom", "custom",
} }
} }
if isAdmin { if isAdmin {
columns = append(columns, "name", "email", "phone", "country_code") columns = append(columns, "name", "email", "phone", "country_code", "type")
} }
if util.ContainsString(columns, "groups") { if util.ContainsString(columns, "groups") {
@ -627,12 +628,10 @@ func AddUser(user *User) (bool, error) {
return false, nil return false, nil
} }
if user.PasswordType == "" && organization.PasswordType != "" { if user.PasswordType == "" || user.PasswordType == "plain" {
user.PasswordType = organization.PasswordType user.UpdateUserPassword(organization)
} }
user.UpdateUserPassword(organization)
err = user.UpdateUserHash() err = user.UpdateUserHash()
if err != nil { if err != nil {
return false, err return false, err
@ -759,6 +758,7 @@ func GetUserInfo(user *User, scope string, aud string, host string) *Userinfo {
} }
if strings.Contains(scope, "email") { if strings.Contains(scope, "email") {
resp.Email = user.Email resp.Email = user.Email
resp.EmailVerified = user.EmailVerified
} }
if strings.Contains(scope, "address") { if strings.Contains(scope, "address") {
resp.Address = user.Location resp.Address = user.Location

View File

@ -1,6 +1,8 @@
package object package object
import ( import (
"fmt"
"github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/errors" "github.com/casbin/casbin/v2/errors"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@ -17,11 +19,28 @@ func NewUserGroupEnforcer(enforcer *casbin.Enforcer) *UserGroupEnforcer {
} }
} }
func (e *UserGroupEnforcer) checkModel() error {
if _, ok := e.enforcer.GetModel()["g"]; !ok {
return fmt.Errorf("The Casbin model used by enforcer doesn't support RBAC (\"[role_definition]\" section not found), please use a RBAC enabled Casbin model for the enforcer")
}
return nil
}
func (e *UserGroupEnforcer) AddGroupForUser(user string, group string) (bool, error) { func (e *UserGroupEnforcer) AddGroupForUser(user string, group string) (bool, error) {
err := e.checkModel()
if err != nil {
return false, err
}
return e.enforcer.AddRoleForUser(user, GetGroupWithPrefix(group)) return e.enforcer.AddRoleForUser(user, GetGroupWithPrefix(group))
} }
func (e *UserGroupEnforcer) AddGroupsForUser(user string, groups []string) (bool, error) { func (e *UserGroupEnforcer) AddGroupsForUser(user string, groups []string) (bool, error) {
err := e.checkModel()
if err != nil {
return false, err
}
g := make([]string, len(groups)) g := make([]string, len(groups))
for i, group := range groups { for i, group := range groups {
g[i] = GetGroupWithPrefix(group) g[i] = GetGroupWithPrefix(group)
@ -30,14 +49,29 @@ func (e *UserGroupEnforcer) AddGroupsForUser(user string, groups []string) (bool
} }
func (e *UserGroupEnforcer) DeleteGroupForUser(user string, group string) (bool, error) { func (e *UserGroupEnforcer) DeleteGroupForUser(user string, group string) (bool, error) {
err := e.checkModel()
if err != nil {
return false, err
}
return e.enforcer.DeleteRoleForUser(user, GetGroupWithPrefix(group)) return e.enforcer.DeleteRoleForUser(user, GetGroupWithPrefix(group))
} }
func (e *UserGroupEnforcer) DeleteGroupsForUser(user string) (bool, error) { func (e *UserGroupEnforcer) DeleteGroupsForUser(user string) (bool, error) {
err := e.checkModel()
if err != nil {
return false, err
}
return e.enforcer.DeleteRolesForUser(user) return e.enforcer.DeleteRolesForUser(user)
} }
func (e *UserGroupEnforcer) GetGroupsForUser(user string) ([]string, error) { func (e *UserGroupEnforcer) GetGroupsForUser(user string) ([]string, error) {
err := e.checkModel()
if err != nil {
return nil, err
}
groups, err := e.enforcer.GetRolesForUser(user) groups, err := e.enforcer.GetRolesForUser(user)
for i, group := range groups { for i, group := range groups {
groups[i] = GetGroupWithoutPrefix(group) groups[i] = GetGroupWithoutPrefix(group)
@ -46,6 +80,11 @@ func (e *UserGroupEnforcer) GetGroupsForUser(user string) ([]string, error) {
} }
func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) { func (e *UserGroupEnforcer) GetAllUsersByGroup(group string) ([]string, error) {
err := e.checkModel()
if err != nil {
return nil, err
}
users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group)) users, err := e.enforcer.GetUsersForRole(GetGroupWithPrefix(group))
if err != nil { if err != nil {
if err == errors.ERR_NAME_NOT_FOUND { if err == errors.ERR_NAME_NOT_FOUND {
@ -65,13 +104,17 @@ func GetGroupWithoutPrefix(group string) string {
} }
func (e *UserGroupEnforcer) GetUserNamesByGroupName(groupName string) ([]string, error) { func (e *UserGroupEnforcer) GetUserNamesByGroupName(groupName string) ([]string, error) {
var names []string err := e.checkModel()
if err != nil {
return nil, err
}
userIds, err := e.GetAllUsersByGroup(groupName) userIds, err := e.GetAllUsersByGroup(groupName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
names := []string{}
for _, userId := range userIds { for _, userId := range userIds {
_, name := util.GetOwnerAndNameFromIdNoCheck(userId) _, name := util.GetOwnerAndNameFromIdNoCheck(userId)
names = append(names, name) names = append(names, name)
@ -81,7 +124,12 @@ func (e *UserGroupEnforcer) GetUserNamesByGroupName(groupName string) ([]string,
} }
func (e *UserGroupEnforcer) UpdateGroupsForUser(user string, groups []string) (bool, error) { func (e *UserGroupEnforcer) UpdateGroupsForUser(user string, groups []string) (bool, error) {
_, err := e.DeleteGroupsForUser(user) err := e.checkModel()
if err != nil {
return false, err
}
_, err = e.DeleteGroupsForUser(user)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -73,8 +73,8 @@ func parseListItem(lines *[]string, i int) []string {
return trimmedItems return trimmedItems
} }
func UploadUsers(owner string, fileId string) (bool, error) { func UploadUsers(owner string, path string) (bool, error) {
table := xlsx.ReadXlsxFile(fileId) table := xlsx.ReadXlsxFile(path)
oldUserMap, err := getUserMap(owner) oldUserMap, err := getUserMap(owner)
if err != nil { if err != nil {

View File

@ -19,12 +19,22 @@ import (
"strings" "strings"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
"github.com/casvisor/casvisor-go-sdk/casvisorsdk"
) )
func sendWebhook(webhook *Webhook, record *Record) error { func sendWebhook(webhook *Webhook, record *casvisorsdk.Record, extendedUser *User) error {
client := &http.Client{} client := &http.Client{}
body := strings.NewReader(util.StructToJson(record)) type RecordEx struct {
casvisorsdk.Record
ExtendedUser *User `xorm:"-" json:"extendedUser"`
}
recordEx := &RecordEx{
Record: *record,
ExtendedUser: extendedUser,
}
body := strings.NewReader(util.StructToJson(recordEx))
req, err := http.NewRequest(webhook.Method, webhook.Url, body) req, err := http.NewRequest(webhook.Method, webhook.Url, body)
if err != nil { if err != nil {

View File

@ -14,10 +14,7 @@
package pp package pp
import ( import "net/http"
"fmt"
"net/http"
)
type DummyPaymentProvider struct{} type DummyPaymentProvider struct{}
@ -27,8 +24,7 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
} }
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
payUrl := fmt.Sprintf("/payments/%s/result", paymentName) return returnUrl, "", nil
return payUrl, "", nil
} }
func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { func (pp *DummyPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {

View File

@ -114,8 +114,8 @@ func (pp *PaypalPaymentProvider) Notify(request *http.Request, body []byte, auth
if err != nil { if err != nil {
return nil, err return nil, err
} }
if captureRsp.Code != paypal.Success { if detailRsp.Code != paypal.Success {
errDetail := captureRsp.ErrorResponse.Details[0] errDetail := detailRsp.ErrorResponse.Details[0]
switch errDetail.Issue { switch errDetail.Issue {
case "ORDER_NOT_APPROVED": case "ORDER_NOT_APPROVED":
notifyResult.PaymentStatus = PaymentStateCanceled notifyResult.PaymentStatus = PaymentStateCanceled

View File

@ -14,9 +14,7 @@
package pp package pp
import ( import "net/http"
"net/http"
)
type PaymentState string type PaymentState string

View File

@ -17,6 +17,7 @@ package pp
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"net/http" "net/http"
"github.com/casdoor/casdoor/util" "github.com/casdoor/casdoor/util"
@ -31,17 +32,22 @@ type WechatPayNotifyResponse struct {
type WechatPaymentProvider struct { type WechatPaymentProvider struct {
Client *wechat.ClientV3 Client *wechat.ClientV3
appId string AppId string
} }
func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCertSerialNumber string, privateKey string) (*WechatPaymentProvider, error) { func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, serialNo string, privateKey string) (*WechatPaymentProvider, error) {
if appId == "" && mchId == "" && mchCertSerialNumber == "" && apiV3Key == "" && privateKey == "" { // https://pay.weixin.qq.com/docs/merchant/products/native-payment/preparation.html
// clientId => mchId
// clientSecret => apiV3Key
// clientId2 => appId
// appCertificate => serialNo
// appPrivateKey => privateKey
if appId == "" || mchId == "" || serialNo == "" || apiV3Key == "" || privateKey == "" {
return &WechatPaymentProvider{}, nil return &WechatPaymentProvider{}, nil
} }
pp := &WechatPaymentProvider{appId: appId} clientV3, err := wechat.NewClientV3(mchId, serialNo, apiV3Key, privateKey)
clientV3, err := wechat.NewClientV3(mchId, mchCertSerialNumber, apiV3Key, privateKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -50,73 +56,70 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, mchCe
if err != nil { if err != nil {
return nil, err return nil, err
} }
pp := &WechatPaymentProvider{
pp.Client = clientV3.SetPlatformCert([]byte(platformCert), serialNo) Client: clientV3.SetPlatformCert([]byte(platformCert), serialNo),
AppId: appId,
}
return pp, nil return pp, nil
} }
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) { func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
// pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{} bm := gopay.BodyMap{}
bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName})) bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName}))
bm.Set("appid", pp.appId) bm.Set("appid", pp.AppId)
bm.Set("description", productDisplayName) bm.Set("description", productDisplayName)
bm.Set("notify_url", notifyUrl) bm.Set("notify_url", notifyUrl)
bm.Set("out_trade_no", paymentName) bm.Set("out_trade_no", paymentName)
bm.SetBodyMap("amount", func(bm gopay.BodyMap) { bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", int(price*100)) bm.Set("total", priceFloat64ToInt64(price))
bm.Set("currency", "CNY") bm.Set("currency", currency)
}) })
wxRsp, err := pp.Client.V3TransactionNative(context.Background(), bm) nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
if nativeRsp.Code != wechat.Success {
if wxRsp.Code != wechat.Success { return "", "", errors.New(nativeRsp.Error)
return "", "", errors.New(wxRsp.Error)
} }
return wxRsp.Response.CodeUrl, "", nil return nativeRsp.Response.CodeUrl, paymentName, nil // Wechat can use paymentName as the OutTradeNo to query order status
} }
func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) { func (pp *WechatPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string, orderId string) (*NotifyResult, error) {
notifyReq, err := wechat.V3ParseNotify(request) notifyResult := &NotifyResult{}
queryRsp, err := pp.Client.V3TransactionQueryOrder(context.Background(), wechat.OutTradeNo, orderId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if queryRsp.Code != wechat.Success {
cert := pp.Client.WxPublicKey() return nil, errors.New(queryRsp.Error)
err = notifyReq.VerifySignByPK(cert)
if err != nil {
return nil, err
} }
apiKey := string(pp.Client.ApiV3Key) switch queryRsp.Response.TradeState {
result, err := notifyReq.DecryptCipherText(apiKey) case "SUCCESS":
if err != nil { // skip
return nil, err case "CLOSED":
notifyResult.PaymentStatus = PaymentStateCanceled
return notifyResult, nil
case "NOTPAY", "USERPAYING": // not-pad: waiting for user to pay; user-paying: user is paying
notifyResult.PaymentStatus = PaymentStateCreated
return notifyResult, nil
default:
notifyResult.PaymentStatus = PaymentStateError
notifyResult.NotifyMessage = fmt.Sprintf("unexpected wechat trade state: %v", queryRsp.Response.TradeState)
return notifyResult, nil
} }
productDisplayName, productName, providerName, _ := parseAttachString(queryRsp.Response.Attach)
paymentName := result.OutTradeNo notifyResult = &NotifyResult{
price := float64(result.Amount.PayerTotal) / 100
productDisplayName, productName, providerName, err := parseAttachString(result.Attach)
if err != nil {
return nil, err
}
notifyResult := &NotifyResult{
ProductName: productName, ProductName: productName,
ProductDisplayName: productDisplayName, ProductDisplayName: productDisplayName,
ProviderName: providerName, ProviderName: providerName,
OrderId: orderId, OrderId: orderId,
Price: price, Price: priceInt64ToFloat64(int64(queryRsp.Response.Amount.Total)),
PaymentStatus: PaymentStatePaid, PaymentStatus: PaymentStatePaid,
PaymentName: paymentName, PaymentName: queryRsp.Response.OutTradeNo,
} }
return notifyResult, nil return notifyResult, nil
} }

View File

@ -29,7 +29,13 @@ func AutoSigninFilter(ctx *context.Context) {
// GET parameter like "/page?access_token=123" or // GET parameter like "/page?access_token=123" or
// HTTP Bearer token like "Authorization: Bearer 123" // HTTP Bearer token like "Authorization: Bearer 123"
accessToken := util.GetMaxLenStr(ctx.Input.Query("accessToken"), ctx.Input.Query("access_token"), parseBearerToken(ctx)) accessToken := ctx.Input.Query("accessToken")
if accessToken == "" {
accessToken = ctx.Input.Query("access_token")
}
if accessToken == "" {
accessToken = parseBearerToken(ctx)
}
if accessToken != "" { if accessToken != "" {
token, err := object.GetTokenByAccessToken(accessToken) token, err := object.GetTokenByAccessToken(accessToken)

View File

@ -181,9 +181,6 @@ func initAPI() {
beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken") beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken") beego.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken") beego.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")
beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord")
beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions") beego.Router("/api/get-sessions", &controllers.ApiController{}, "GET:GetSessions")
beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession") beego.Router("/api/get-session", &controllers.ApiController{}, "GET:GetSingleSession")
@ -276,7 +273,7 @@ func initAPI() {
beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy") beego.Router("/cas/:organization/:application/proxy", &controllers.RootController{}, "GET:CasProxy")
beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate") beego.Router("/cas/:organization/:application/validate", &controllers.RootController{}, "GET:CasValidate")
beego.Router("/cas/:organization/:application/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate") beego.Router("/cas/:organization/:application/p3/serviceValidate", &controllers.RootController{}, "GET:CasP3ServiceValidate")
beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ServiceAndProxyValidate") beego.Router("/cas/:organization/:application/p3/proxyValidate", &controllers.RootController{}, "GET:CasP3ProxyValidate")
beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate") beego.Router("/cas/:organization/:application/samlValidate", &controllers.RootController{}, "POST:SamlValidate")
} }

View File

@ -16,6 +16,7 @@ package routers
import ( import (
"compress/gzip" "compress/gzip"
"fmt"
"io" "io"
"net/http" "net/http"
"os" "os"
@ -59,6 +60,13 @@ func StaticFilter(ctx *context.Context) {
path = "web/build/index.html" path = "web/build/index.html"
} }
if !util.FileExist(path) { if !util.FileExist(path) {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
dir = strings.ReplaceAll(dir, "\\", "/")
errorText := fmt.Sprintf("The Casdoor frontend HTML file: \"index.html\" was not found, it should be placed at: \"%s/web/build/index.html\". For more information, see: https://casdoor.org/docs/basic/server-installation/#frontend-1", dir)
http.ServeContent(ctx.ResponseWriter, ctx.Request, "Casdoor frontend has encountered error...", time.Now(), strings.NewReader(errorText))
return return
} }

View File

@ -15,6 +15,7 @@
package storage package storage
import ( import (
"fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
@ -23,76 +24,69 @@ import (
"github.com/casdoor/oss" "github.com/casdoor/oss"
) )
var baseFolder = "files" // LocalFileSystemProvider file system storage
type LocalFileSystemProvider struct {
// FileSystem file system storage BaseDir string
type FileSystem struct {
Base string
} }
// NewFileSystem initialize the local file system storage // NewLocalFileSystemStorageProvider initialize the local file system storage
func NewFileSystem(base string) *FileSystem { func NewLocalFileSystemStorageProvider() *LocalFileSystemProvider {
absBase, err := filepath.Abs(base) baseFolder := "files"
absBase, err := filepath.Abs(baseFolder)
if err != nil { if err != nil {
panic("local file system storage's base folder is not initialized") panic(err)
} }
return &FileSystem{Base: absBase} return &LocalFileSystemProvider{BaseDir: absBase}
} }
// GetFullPath get full path from absolute/relative path // GetFullPath get full path from absolute/relative path
func (fileSystem FileSystem) GetFullPath(path string) string { func (sp LocalFileSystemProvider) GetFullPath(path string) string {
fullPath := path fullPath := path
if !strings.HasPrefix(path, fileSystem.Base) { if !strings.HasPrefix(path, sp.BaseDir) {
fullPath, _ = filepath.Abs(filepath.Join(fileSystem.Base, path)) fullPath, _ = filepath.Abs(filepath.Join(sp.BaseDir, path))
} }
return fullPath return fullPath
} }
// Get receive file with given path // Get receive file with given path
func (fileSystem FileSystem) Get(path string) (*os.File, error) { func (sp LocalFileSystemProvider) Get(path string) (*os.File, error) {
return os.Open(fileSystem.GetFullPath(path)) return os.Open(sp.GetFullPath(path))
} }
// GetStream get file as stream // GetStream get file as stream
func (fileSystem FileSystem) GetStream(path string) (io.ReadCloser, error) { func (sp LocalFileSystemProvider) GetStream(path string) (io.ReadCloser, error) {
return os.Open(fileSystem.GetFullPath(path)) return os.Open(sp.GetFullPath(path))
} }
// Put store a reader into given path // Put store a reader into given path
func (fileSystem FileSystem) Put(path string, reader io.Reader) (*oss.Object, error) { func (sp LocalFileSystemProvider) Put(path string, reader io.Reader) (*oss.Object, error) {
var ( fullPath := sp.GetFullPath(path)
fullPath = fileSystem.GetFullPath(path)
err = os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
)
err := os.MkdirAll(filepath.Dir(fullPath), os.ModePerm)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("Casdoor fails to create folder: \"%s\" for local file system storage provider: %s. Make sure Casdoor process has correct permission to create/access it, or you can create it manually in advance", filepath.Dir(fullPath), err.Error())
} }
dst, err := os.Create(filepath.Clean(fullPath)) dst, err := os.Create(filepath.Clean(fullPath))
if err == nil { if err == nil {
if seeker, ok := reader.(io.ReadSeeker); ok { if seeker, ok := reader.(io.ReadSeeker); ok {
seeker.Seek(0, 0) seeker.Seek(0, 0)
} }
_, err = io.Copy(dst, reader) _, err = io.Copy(dst, reader)
} }
return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: sp}, err
return &oss.Object{Path: path, Name: filepath.Base(path), StorageInterface: fileSystem}, err
} }
// Delete delete file // Delete delete file
func (fileSystem FileSystem) Delete(path string) error { func (sp LocalFileSystemProvider) Delete(path string) error {
return os.Remove(fileSystem.GetFullPath(path)) return os.Remove(sp.GetFullPath(path))
} }
// List list all objects under current path // List list all objects under current path
func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) { func (sp LocalFileSystemProvider) List(path string) ([]*oss.Object, error) {
var ( objects := []*oss.Object{}
objects []*oss.Object fullPath := sp.GetFullPath(path)
fullPath = fileSystem.GetFullPath(path)
)
filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error { filepath.Walk(fullPath, func(path string, info os.FileInfo, err error) error {
if path == fullPath { if path == fullPath {
@ -102,10 +96,10 @@ func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
if err == nil && !info.IsDir() { if err == nil && !info.IsDir() {
modTime := info.ModTime() modTime := info.ModTime()
objects = append(objects, &oss.Object{ objects = append(objects, &oss.Object{
Path: strings.TrimPrefix(path, fileSystem.Base), Path: strings.TrimPrefix(path, sp.BaseDir),
Name: info.Name(), Name: info.Name(),
LastModified: &modTime, LastModified: &modTime,
StorageInterface: fileSystem, StorageInterface: sp,
}) })
} }
return nil return nil
@ -114,16 +108,12 @@ func (fileSystem FileSystem) List(path string) ([]*oss.Object, error) {
return objects, nil return objects, nil
} }
// GetEndpoint get endpoint, FileSystem's endpoint is / // GetEndpoint get endpoint, LocalFileSystemProvider's endpoint is /
func (fileSystem FileSystem) GetEndpoint() string { func (sp LocalFileSystemProvider) GetEndpoint() string {
return "/" return "/"
} }
// GetURL get public accessible URL // GetURL get public accessible URL
func (fileSystem FileSystem) GetURL(path string) (url string, err error) { func (sp LocalFileSystemProvider) GetURL(path string) (url string, err error) {
return path, nil return path, nil
} }
func NewLocalFileSystemStorageProvider(clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
return NewFileSystem(baseFolder)
}

View File

@ -19,7 +19,7 @@ import "github.com/casdoor/oss"
func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface { func GetStorageProvider(providerType string, clientId string, clientSecret string, region string, bucket string, endpoint string) oss.StorageInterface {
switch providerType { switch providerType {
case "Local File System": case "Local File System":
return NewLocalFileSystemStorageProvider(clientId, clientSecret, region, bucket, endpoint) return NewLocalFileSystemStorageProvider()
case "AWS S3": case "AWS S3":
return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint) return NewAwsS3StorageProvider(clientId, clientSecret, region, bucket, endpoint)
case "MinIO": case "MinIO":

View File

@ -34,16 +34,6 @@ func GetPath(path string) string {
return filepath.Dir(path) return filepath.Dir(path)
} }
func EnsureFileFolderExists(path string) {
p := GetPath(path)
if !FileExist(p) {
err := os.MkdirAll(p, os.ModePerm)
if err != nil {
panic(err)
}
}
}
func ListFiles(path string) []string { func ListFiles(path string) []string {
res := []string{} res := []string{}

View File

@ -14,8 +14,13 @@
package util package util
import "fmt" import "io/ioutil"
func GetUploadXlsxPath(fileId string) string { func GetUploadXlsxPath(fileId string) string {
return fmt.Sprintf("tmpFiles/%s.xlsx", fileId) file, err := ioutil.TempFile("", fileId)
if err != nil {
panic(err)
}
return file.Name()
} }

View File

@ -1,40 +0,0 @@
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetUploadXlsxPath(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"scenery one", "casdoor", "tmpFiles/casdoor.xlsx"},
{"scenery two", "casbin", "tmpFiles/casbin.xlsx"},
{"scenery three", "loremIpsum", "tmpFiles/loremIpsum.xlsx"},
{"scenery four", "", "tmpFiles/.xlsx"},
}
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual := GetUploadXlsxPath(scenery.input)
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
})
}
}

View File

@ -23,6 +23,7 @@ import (
"math/rand" "math/rand"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -186,32 +187,6 @@ func IsStringsEmpty(strs ...string) bool {
return false return false
} }
func GetMaxLenStr(strs ...string) string {
m := 0
i := 0
for j, str := range strs {
l := len(str)
if l > m {
m = l
i = j
}
}
return strs[i]
}
func GetMinLenStr(strs ...string) string {
m := int(^uint(0) >> 1)
i := 0
for j, str := range strs {
l := len(str)
if l < m {
m = l
i = j
}
}
return strs[i]
}
func ReadStringFromPath(path string) string { func ReadStringFromPath(path string) string {
data, err := os.ReadFile(filepath.Clean(path)) data, err := os.ReadFile(filepath.Clean(path))
if err != nil { if err != nil {
@ -315,3 +290,13 @@ func ParseIdToString(input interface{}) (string, error) {
return "", fmt.Errorf("unsupported id type: %T", input) return "", fmt.Errorf("unsupported id type: %T", input)
} }
} }
func GetValueFromDataSourceName(key string, dataSourceName string) string {
reg := regexp.MustCompile(key + "=([^ ]+)")
matches := reg.FindStringSubmatch(dataSourceName)
if len(matches) >= 2 {
return matches[1]
}
return ""
}

View File

@ -189,45 +189,6 @@ func TestIsStrsEmpty(t *testing.T) {
} }
} }
func TestGetMaxLenStr(t *testing.T) {
scenarios := []struct {
description string
input []string
expected interface{}
}{
{"Should be return casdoor", []string{"", "casdoor", "casbin"}, "casdoor"},
{"Should be return casdoor_jdk", []string{"", "casdoor", "casbin", "casdoor_jdk"}, "casdoor_jdk"},
{"Should be return empty string", []string{""}, ""},
}
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual := GetMaxLenStr(scenery.input...)
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
})
}
}
func TestGetMinLenStr(t *testing.T) {
scenarios := []struct {
description string
input []string
expected interface{}
}{
{"Should be return casbin", []string{"casdoor", "casbin"}, "casbin"},
{"Should be return casbin", []string{"casdoor", "casbin", "casdoor_jdk"}, "casbin"},
{"Should be return empty string", []string{"a", "", "casbin"}, ""},
{"Should be return a", []string{"a", "casdoor", "casbin"}, "a"},
{"Should be return a", []string{"casdoor", "a", "casbin"}, "a"},
{"Should be return a", []string{"casbin", "casdoor", "a"}, "a"},
}
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual := GetMinLenStr(scenery.input...)
assert.Equal(t, scenery.expected, actual, "The returned value not is expected")
})
}
}
func TestSnakeString(t *testing.T) { func TestSnakeString(t *testing.T) {
scenarios := []struct { scenarios := []struct {
description string description string

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