Compare commits

...

37 Commits

Author SHA1 Message Date
cd76e9372e feat: delete the old token when refreshing token (#617)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-24 19:58:12 +08:00
04b9e05244 fix: WeComInternalIdProvider GetUserInfo method could not get the correct user id (#616) 2022-03-24 17:53:05 +08:00
a78b2de7b2 fix: panic when not select one provider (#614)
Signed-off-by: Sagilio <Sagilio@outlook.com>
2022-03-24 12:15:10 +08:00
d0952ae908 fix: docker-compose up can't work on linux (#606) 2022-03-22 18:43:02 +08:00
ade64693e4 fix: support lower go version(1.15) (#599)
* fix: support lower go version(1.15)

* fix: support lower go version(1.15)

* fix: support lower go version(1.15)
2022-03-21 21:55:16 +08:00
5f8924ed4e feat: support overriding configuration with env (#590) 2022-03-20 23:21:09 +08:00
1a6d98d029 refactor: New Crowdin translations by Github Action (#592)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-20 22:30:29 +08:00
447dd1c534 feat: update the uploaded user field and provide demo xlsx file (#596)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 22:28:22 +08:00
86b5d72e5d fix: concatChar assignment logic (#595)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 11:54:14 +08:00
6bc4e646e5 fix: oAuthParams may not exist (#594)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-20 10:33:50 +08:00
0841eb5c30 Fix !skipCi directive. 2022-03-19 23:15:19 +08:00
4015c221f7 refactor: New Crowdin translations by Github Action (#588)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2022-03-19 22:01:20 +08:00
dcd6328498 fix: callback url param missing (#583)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-19 20:01:44 +08:00
8080927890 fix: redirect for non-built-in app logout (#587)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-19 19:50:05 +08:00
a95c5b05a9 Remove GitHub provider hacking code. 2022-03-19 19:43:54 +08:00
865a65d399 fix: fix the params problem in code signin (#577)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-18 20:12:29 +08:00
e8b9c67671 feat: add casdoor as itself idp support (#578)
Signed-off-by: Steve0x2a <stevesough@gmail.com>
2022-03-18 18:28:46 +08:00
e5ff49f7a7 fix: UI bug after switching to English (#570) 2022-03-15 21:02:54 +08:00
9f7924a6e0 fix: mask email and phone number on the backend (#563)
* fix: mask email and phone number on the backend

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: login with masked email or phone

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>

* fix: improve regex

Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-15 12:54:57 +08:00
377e200837 fix: repair the problem that AutoSigninFilter middleware doesn't recognize the access_token request parameter (#569)
AutoSigninFilter method only checks for `accessToken` request parameters or `Authorization` request header, doesn't recognize `access_token` request parameters, now added, use `utils.GetMaxLenStr()` method to get the maximum length characters
2022-03-15 12:52:44 +08:00
93a76de044 fix: fix compile error in low go version (#568) 2022-03-15 12:49:12 +08:00
35bef969fd feat: support Huawei Cloud SMS (#565)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-14 20:49:03 +08:00
4dca3bd3f7 Add Notify() to payment provider. 2022-03-14 02:56:04 +08:00
5de417ecf7 Add gc provider. 2022-03-14 00:32:36 +08:00
bf24594fb4 Make resource name longer. 2022-03-13 21:20:00 +08:00
4a87b4790e Avoid panic in AddUsers(). 2022-03-13 20:53:05 +08:00
fde8c4b5f6 Fix NotifyPayment(). 2022-03-13 19:57:23 +08:00
55a84644e1 Add PaymentResultPage. 2022-03-13 18:05:16 +08:00
ca87dd7dea Add returnUrl to product. 2022-03-13 16:25:54 +08:00
32af4a766e Add GetUserPayments() API. 2022-03-13 14:56:21 +08:00
4d035bf66d Add tags to organization. 2022-03-13 00:35:49 +08:00
743dcc9725 Fix translation. 2022-03-12 23:37:58 +08:00
d43d7d1ae9 feat: support master password for ldap user (#561)
Signed-off-by: Yixiang Zhao <seriouszyx@foxmail.com>
2022-03-12 21:06:38 +08:00
c906f1e5d2 Add user and state to payment pages. 2022-03-12 20:03:58 +08:00
37a26e2a91 Fix delete-resource authz check. 2022-03-11 11:27:52 +08:00
e7018e3de4 docs: add a tip to create db for the first time (#550)
* add a tip to create db schema ahead of time

* add a tip to create db schema ahead of time

* docs: add a tip to create db schema ahead of time
2022-03-10 11:03:52 +08:00
3a64e4dcd8 docs: add a tip to create db schema ahead of time (#547) 2022-03-10 09:58:00 +08:00
98 changed files with 1892 additions and 350 deletions

View File

@ -82,6 +82,14 @@ Edit `conf/app.conf`, modify `dataSourceName` to correct database info, which fo
username:password@tcp(database_ip:database_port)/
```
Then create an empty schema (database) named `casdoor` in your relational database. After the program runs for the first time, it will automatically create tables in this schema.
You can also edit `main.go`, modify `false` to `true`. It will automatically create the schema (database) named `casdoor` in this database.
```bash
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
```
#### Run
Casdoor provides two run modes, the difference is binary size and user prompt.

View File

@ -15,7 +15,6 @@
package authz
import (
"github.com/astaxie/beego"
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin/v2/model"
xormadapter "github.com/casbin/xorm-adapter/v2"
@ -28,8 +27,8 @@ var Enforcer *casbin.Enforcer
func InitAuthz() {
var err error
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName()+beego.AppConfig.String("dbName"), "casbin_rule", tableNamePrefix, true)
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
a, err := xormadapter.NewAdapterWithTableName(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName()+conf.GetConfigString("dbName"), "casbin_rule", tableNamePrefix, true)
if err != nil {
panic(err)
}
@ -88,6 +87,10 @@ p, *, *, GET, /api/get-application, *, *
p, *, *, GET, /api/get-user, *, *
p, *, *, GET, /api/get-user-application, *, *
p, *, *, GET, /api/get-resources, *, *
p, *, *, GET, /api/get-product, *, *
p, *, *, POST, /api/buy-product, *, *
p, *, *, GET, /api/get-payment, *, *
p, *, *, GET, /api/get-providers, *, *
p, *, *, POST, /api/unlink, *, *
p, *, *, POST, /api/set-password, *, *
p, *, *, POST, /api/send-verification-code, *, *

View File

@ -15,14 +15,49 @@
package conf
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/astaxie/beego"
)
func GetConfigString(key string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
return beego.AppConfig.String(key)
}
func GetConfigBool(key string) (bool, error) {
value := GetConfigString(key)
if value == "true" {
return true, nil
} else if value == "false" {
return false, nil
}
return false, fmt.Errorf("value %s cannot be converted into bool", value)
}
func GetConfigInt64(key string) (int64, error) {
value := GetConfigString(key)
num, err := strconv.ParseInt(value, 10, 64)
return num, err
}
func init() {
//this array contains the beego configuration items that may be modified via env
var presetConfigItems = []string{"httpport", "appname"}
for _, key := range presetConfigItems {
if value, ok := os.LookupEnv(key); ok {
beego.AppConfig.Set(key, value)
}
}
}
func GetBeegoConfDataSourceName() string {
dataSourceName := beego.AppConfig.String("dataSourceName")
dataSourceName := GetConfigString("dataSourceName")
runningInDocker := os.Getenv("RUNNING_IN_DOCKER")
if runningInDocker == "true" {

98
conf/conf_test.go Normal file
View File

@ -0,0 +1,98 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package conf
import (
"os"
"testing"
"github.com/astaxie/beego"
"github.com/stretchr/testify/assert"
)
func TestGetConfString(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"Should be return casbin", "appname", "casbin"},
{"Should be return 8000", "httpport", "8000"},
{"Should be return value", "key", "value"},
}
//do some set up job
os.Setenv("appname", "casbin")
os.Setenv("key", "value")
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual := GetConfigString(scenery.input)
assert.Equal(t, scenery.expected, actual)
})
}
}
func TestGetConfInt(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"Should be return 8000", "httpport", 8001},
{"Should be return 8000", "verificationCodeTimeout", 10},
}
//do some set up job
os.Setenv("httpport", "8001")
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual, err := GetConfigInt64(scenery.input)
assert.Nil(t, err)
assert.Equal(t, scenery.expected, int(actual))
})
}
}
func TestGetConfBool(t *testing.T) {
scenarios := []struct {
description string
input string
expected interface{}
}{
{"Should be return false", "SessionOn", false},
{"Should be return false", "copyrequestbody", true},
}
//do some set up job
os.Setenv("SessionOn", "false")
err := beego.LoadAppConfig("ini", "app.conf")
assert.Nil(t, err)
for _, scenery := range scenarios {
t.Run(scenery.description, func(t *testing.T) {
actual, err := GetConfigBool(scenery.input)
assert.Nil(t, err)
assert.Equal(t, scenery.expected, actual)
})
}
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
@ -172,6 +173,13 @@ func (c *ApiController) Signup() {
Karma: 0,
}
if len(organization.Tags) > 0 {
tokens := strings.Split(organization.Tags[0], "|")
if len(tokens) > 0 {
user.Tag = tokens[0]
}
}
if application.GetSignupItemRule("Display name") == "First, last" {
if form.FirstName != "" || form.LastName != "" {
user.DisplayName = fmt.Sprintf("%s %s", form.FirstName, form.LastName)
@ -217,10 +225,15 @@ func (c *ApiController) Logout() {
user := c.GetSessionUsername()
util.LogInfo(c.Ctx, "API: [%s] logged out", user)
application := c.GetSessionApplication()
c.SetSessionUsername("")
c.SetSessionData(nil)
c.ResponseOk(user)
if application == nil || application.Name == "app-built-in" || application.HomepageUrl == "" {
c.ResponseOk(user)
return
}
c.ResponseOk(user, application.HomepageUrl)
}
// GetAccount

View File

@ -23,7 +23,7 @@ import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
@ -167,9 +167,16 @@ func (c *ApiController) Login() {
var verificationCodeType string
var checkResult string
if form.Name != "" {
user = object.GetUserByFields(form.Organization, form.Name)
}
// check result through Email or Phone
if strings.Contains(form.Username, "@") {
verificationCodeType = "email"
if user != nil && util.GetMaskedEmail(user.Email) == form.Username {
form.Username = user.Email
}
checkResult = object.CheckVerificationCode(form.Username, form.Code)
} else {
verificationCodeType = "phone"
@ -178,6 +185,9 @@ func (c *ApiController) Login() {
c.ResponseError(responseText)
return
}
if user != nil && util.GetMaskedPhone(user.Phone) == form.Username {
form.Username = user.Phone
}
checkPhone := fmt.Sprintf("+%s%s", form.PhonePrefix, form.Username)
checkResult = object.CheckVerificationCode(checkPhone, form.Code)
}
@ -257,8 +267,8 @@ func (c *ApiController) Login() {
setHttpClient(idProvider, provider.Type)
if form.State != beego.AppConfig.String("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", beego.AppConfig.String("authState"), form.State))
if form.State != conf.GetConfigString("authState") && form.State != application.Name {
c.ResponseError(fmt.Sprintf("state expected: \"%s\", but got: \"%s\"", conf.GetConfigString("authState"), form.State))
return
}

View File

@ -72,6 +72,15 @@ func (c *ApiController) GetSessionUsername() string {
return user.(string)
}
func (c *ApiController) GetSessionApplication() *object.Application {
clientId := c.GetSession("aud")
if clientId == nil {
return nil
}
application := object.GetApplicationByClientId(clientId.(string))
return application
}
func (c *ApiController) GetSessionOidc() (string, string) {
sessionData := c.GetSessionData()
if sessionData != nil &&

View File

@ -178,7 +178,7 @@ func (c *ApiController) UpdateLdap() {
}
if ldap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StartAutoSync(ldap.Id)
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0{
} else if ldap.AutoSync == 0 && prevLdap.AutoSync != 0 {
object.GetLdapAutoSynchronizer().StopAutoSync(ldap.Id)
}

View File

@ -21,7 +21,6 @@ import (
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay/alipay"
)
// GetPayments
@ -50,6 +49,24 @@ func (c *ApiController) GetPayments() {
}
}
// GetUserPayments
// @Title GetUserPayments
// @Tag Payment API
// @Description get payments for a user
// @Param owner query string true "The owner of payments"
// @Param organization query string true "The organization of the user"
// @Param user query string true "The username of the user"
// @Success 200 {array} object.Payment The Response object
// @router /get-user-payments [get]
func (c *ApiController) GetUserPayments() {
owner := c.Input().Get("owner")
organization := c.Input().Get("organization")
user := c.Input().Get("user")
payments := object.GetUserPayments(owner, organization, user)
c.ResponseOk(payments)
}
// @Title GetPayment
// @Tag Payment API
// @Description get payment
@ -124,14 +141,16 @@ func (c *ApiController) DeletePayment() {
// @Success 200 {object} controllers.Response The Response object
// @router /notify-payment [post]
func (c *ApiController) NotifyPayment() {
bm, err := alipay.ParseNotifyToBodyMap(c.Ctx.Request)
if err != nil {
panic(err)
}
owner := c.Ctx.Input.Param(":owner")
providerName := c.Ctx.Input.Param(":provider")
productName := c.Ctx.Input.Param(":product")
paymentName := c.Ctx.Input.Param(":payment")
ok := object.NotifyPayment(bm)
body := c.Ctx.Input.RequestBody
ok := object.NotifyPayment(c.Ctx.Request, body, owner, providerName, productName, paymentName)
if ok {
_, err = c.Ctx.ResponseWriter.Write([]byte("success"))
_, err := c.Ctx.ResponseWriter.Write([]byte("success"))
if err != nil {
panic(err)
}

View File

@ -16,6 +16,7 @@ package controllers
import (
"encoding/json"
"fmt"
"github.com/astaxie/beego/utils/pagination"
"github.com/casdoor/casdoor/object"
@ -118,16 +119,28 @@ func (c *ApiController) DeleteProduct() {
// @Title BuyProduct
// @Tag Product API
// @Description buy product
// @Param id query string true "The id of the product"
// @Param providerId query string true "The id of the provider"
// @Param id query string true "The id of the product"
// @Param providerName query string true "The name of the provider"
// @Success 200 {object} controllers.Response The Response object
// @router /buy-product [post]
func (c *ApiController) BuyProduct() {
id := c.Input().Get("id")
providerId := c.Input().Get("providerId")
providerName := c.Input().Get("providerName")
host := c.Ctx.Request.Host
payUrl, err := object.BuyProduct(id, providerId, host)
userId := c.GetSessionUsername()
if userId == "" {
c.ResponseError("Please login first")
return
}
user := object.GetUser(userId)
if user == nil {
c.ResponseError(fmt.Sprintf("The user: %s doesn't exist", userId))
return
}
payUrl, err := object.BuyProduct(id, providerName, user, host)
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -194,15 +194,19 @@ func (c *ApiController) GetEmailAndPhone() {
return
}
respUser := object.User{Email: user.Email, Phone: user.Phone, Name: user.Name}
respUser := object.User{Name: user.Name}
var contentType string
switch form.Username {
case user.Email:
contentType = "email"
respUser.Email = user.Email
case user.Phone:
contentType = "phone"
respUser.Phone = user.Phone
case user.Name:
contentType = "username"
respUser.Email = util.GetMaskedEmail(user.Email)
respUser.Phone = util.GetMaskedPhone(user.Phone)
}
c.ResponseOk(respUser, contentType)

View File

@ -18,7 +18,7 @@ import (
"fmt"
"strconv"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
@ -62,7 +62,7 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
}
func getInitScore() int {
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
score, err := strconv.Atoi(conf.GetConfigString("initScore"))
if err != nil {
panic(err)
}

View File

@ -74,8 +74,16 @@ func (c *ApiController) SendVerificationCode() {
}
sendResp := errors.New("Invalid dest type")
if user == nil && checkUser != "" && checkUser != "true" {
_, name := util.GetOwnerAndNameFromId(orgId)
user = object.GetUser(fmt.Sprintf("%s/%s", name, checkUser))
}
switch destType {
case "email":
if user != nil && util.GetMaskedEmail(user.Email) == dest {
dest = user.Email
}
if !util.IsEmailValid(dest) {
c.ResponseError("Invalid Email address")
return
@ -84,6 +92,9 @@ func (c *ApiController) SendVerificationCode() {
provider := application.GetEmailProvider()
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
case "phone":
if user != nil && util.GetMaskedPhone(user.Phone) == dest {
dest = user.Phone
}
if !util.IsPhoneCnValid(dest) {
c.ResponseError("Invalid phone number")
return

View File

@ -11,6 +11,8 @@ services:
- db
environment:
RUNNING_IN_DOCKER: "true"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./conf:/conf/
db:

2
go.mod
View File

@ -9,7 +9,7 @@ require (
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/casbin/casbin/v2 v2.30.1
github.com/casbin/xorm-adapter/v2 v2.5.1
github.com/casdoor/go-sms-sender v0.0.5
github.com/casdoor/go-sms-sender v0.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-ldap/ldap/v3 v3.3.0

2
go.sum
View File

@ -81,6 +81,8 @@ github.com/casbin/xorm-adapter/v2 v2.5.1 h1:BkpIxRHKa0s3bSMx173PpuU7oTs+Zw7XmD0B
github.com/casbin/xorm-adapter/v2 v2.5.1/go.mod h1:AeH4dBKHC9/zYxzdPVHhPDzF8LYLqjDdb767CWJoV54=
github.com/casdoor/go-sms-sender v0.0.5 h1:9qhlMM+UoSOvvY7puUULqSHBBA7fbe02Px/tzchQboo=
github.com/casdoor/go-sms-sender v0.0.5/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
github.com/casdoor/go-sms-sender v0.2.0 h1:52bin4EBOPzOee64s9UK7jxd22FODvT9/+Y/Z+PSHpg=
github.com/casdoor/go-sms-sender v0.2.0/go.mod h1:fsZsNnALvFIo+HFcE1U/oCQv4ZT42FdglXKMsEm3WSk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=

View File

@ -19,7 +19,7 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
@ -88,7 +88,7 @@ func (idp *AdfsIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@ -97,7 +97,7 @@ func (idp *BaiduIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

158
idp/casdoor.go Normal file
View File

@ -0,0 +1,158 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package idp
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"time"
"golang.org/x/oauth2"
)
type CasdoorIdProvider struct {
Client *http.Client
Config *oauth2.Config
Host string
}
func NewCasdoorIdProvider(clientId string, clientSecret string, redirectUrl string, hostUrl string) *CasdoorIdProvider {
idp := &CasdoorIdProvider{}
config := idp.getConfig(hostUrl)
config.ClientID = clientId
config.ClientSecret = clientSecret
config.RedirectURL = redirectUrl
idp.Config = config
idp.Host = hostUrl
return idp
}
func (idp *CasdoorIdProvider) SetHttpClient(client *http.Client) {
idp.Client = client
}
func (idp *CasdoorIdProvider) getConfig(hostUrl string) *oauth2.Config {
return &oauth2.Config{
Endpoint: oauth2.Endpoint{
TokenURL: hostUrl + "/api/login/oauth/access_token",
},
Scopes: []string{"openid email profile"},
}
}
type CasdoorToken struct {
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
}
func (idp *CasdoorIdProvider) GetToken(code string) (*oauth2.Token, error) {
resp, err := http.PostForm(idp.Config.Endpoint.TokenURL, url.Values{
"client_id": {idp.Config.ClientID},
"client_secret": {idp.Config.ClientSecret},
"code": {code},
"grant_type": {"authorization_code"},
})
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
pToken := &CasdoorToken{}
err = json.Unmarshal(body, pToken)
if err != nil {
return nil, err
}
//check if token is expired
if pToken.ExpiresIn <= 0 {
return nil, fmt.Errorf("%s", pToken.AccessToken)
}
token := &oauth2.Token{
AccessToken: pToken.AccessToken,
Expiry: time.Unix(time.Now().Unix()+int64(pToken.ExpiresIn), 0),
}
return token, nil
}
/*
{
"sub": "2f80c349-4beb-407f-b1f0-528aac0f1acd",
"iss": "https://door.casbin.com",
"aud": "7a11****0fa2172",
"name": "admin",
"preferred_username": "Admin",
"email": "admin@example.com",
"picture": "https://casbin.org/img/casbin.svg",
"address": "Guangdong",
"phone": "12345678910"
}
*/
type CasdoorUserInfo struct {
Id string `json:"sub"`
Name string `json:"name"`
DisplayName string `json:"preferred_username"`
Email string `json:"email"`
AvatarUrl string `json:"picture"`
Status string `json:"status"`
Msg string `json:"msg"`
}
func (idp *CasdoorIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
cdUserinfo := &CasdoorUserInfo{}
accessToken := token.AccessToken
request, err := http.NewRequest("GET", fmt.Sprintf("%s/api/userinfo", idp.Host), nil)
if err != nil {
return nil, err
}
//add accesstoken to bearer token
request.Header.Add("Authorization", fmt.Sprintf("Bearer %s", accessToken))
resp, err := idp.Client.Do(request)
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, cdUserinfo)
if err != nil {
return nil, err
}
if cdUserinfo.Status != "" {
return nil, fmt.Errorf("err: %s", cdUserinfo.Msg)
}
userInfo := &UserInfo{
Id: cdUserinfo.Id,
Username: cdUserinfo.Name,
DisplayName: cdUserinfo.DisplayName,
Email: cdUserinfo.Email,
AvatarUrl: cdUserinfo.AvatarUrl,
}
return userInfo, nil
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -143,7 +144,7 @@ func (idp *DingTalkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -178,7 +179,7 @@ func (idp *DingTalkIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -92,7 +93,7 @@ func (idp *GiteeIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
rbs, err := io.ReadAll(resp.Body)
rbs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ package idp
import (
"context"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strconv"
"time"
@ -172,7 +172,7 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -85,7 +85,7 @@ func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -209,7 +209,7 @@ func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -19,7 +19,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@ -95,7 +95,7 @@ func (idp *GoogleIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"golang.org/x/oauth2"
@ -69,7 +69,7 @@ func (idp *InfoflowInternalIdProvider) GetToken(code string) (*oauth2.Token, err
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -147,7 +147,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -165,7 +165,7 @@ func (idp *InfoflowInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserIn
return nil, err
}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -143,7 +144,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -161,7 +162,7 @@ func (idp *InfoflowIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, erro
return nil, err
}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -196,7 +197,7 @@ func (idp *InfoflowIdProvider) postWithBody(body interface{}, url string) ([]byt
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,6 +17,7 @@ package idp
import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -168,7 +169,7 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
req.Header.Set("Authorization", "Bearer "+token.AccessToken)
resp, err := idp.Client.Do(req)
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
err = resp.Body.Close()
if err != nil {
return nil, err
@ -200,7 +201,7 @@ func (idp *LarkIdProvider) postWithBody(body interface{}, url string) ([]byte, e
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
@ -84,7 +85,7 @@ func (idp *LinkedInIdProvider) GetToken(code string) (*oauth2.Token, error) {
if err != nil {
return nil, err
}
rbs, err := io.ReadAll(resp.Body)
rbs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -322,7 +323,7 @@ func (idp *LinkedInIdProvider) GetUrlRespWithAuthorization(url, token string) ([
}
}(resp.Body)
bs, err := io.ReadAll(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -78,6 +78,8 @@ func GetIdProvider(typ string, subType string, clientId string, clientSecret str
} else {
return nil
}
} else if typ == "Casdoor" {
return NewCasdoorIdProvider(clientId, clientSecret, redirectUrl, hostUrl)
} else if isGothSupport(typ) {
return NewGothIdProvider(typ, clientId, clientSecret, redirectUrl)
}

View File

@ -18,7 +18,7 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"regexp"
@ -75,7 +75,7 @@ func (idp *QqIdProvider) GetToken(code string) (*oauth2.Token, error) {
}
defer resp.Body.Close()
tokenContent, err := io.ReadAll(resp.Body)
tokenContent, err := ioutil.ReadAll(resp.Body)
re := regexp.MustCompile("token=(.*?)&")
matched := re.FindAllStringSubmatch(string(tokenContent), -1)
@ -145,7 +145,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
openIdBody, err := io.ReadAll(resp.Body)
openIdBody, err := ioutil.ReadAll(resp.Body)
re := regexp.MustCompile("\"openid\":\"(.*?)\"}")
matched := re.FindAllStringSubmatch(string(openIdBody), -1)
@ -161,7 +161,7 @@ func (idp *QqIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
}
defer resp.Body.Close()
userInfoBody, err := io.ReadAll(resp.Body)
userInfoBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@ package idp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"time"
@ -72,7 +72,7 @@ func (idp *WeComInternalIdProvider) GetToken(code string) (*oauth2.Token, error)
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -111,6 +111,7 @@ type WecomInternalUserInfo struct {
Email string `json:"email"`
Avatar string `json:"avatar"`
OpenId string `json:"open_userid"`
UserId string `json:"userid"`
}
func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
@ -122,7 +123,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -143,7 +144,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, err
}
data, err = io.ReadAll(resp.Body)
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -156,7 +157,7 @@ func (idp *WeComInternalIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo,
return nil, fmt.Errorf("userInfoResp.errcode = %d, userInfoResp.errmsg = %s", infoResp.Errcode, infoResp.Errmsg)
}
userInfo := UserInfo{
Id: infoResp.OpenId,
Id: infoResp.UserId,
Username: infoResp.Name,
DisplayName: infoResp.Name,
Email: infoResp.Email,

View File

@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"time"
@ -194,7 +195,7 @@ func (idp *WeComIdProvider) postWithBody(body interface{}, url string) ([]byte,
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strconv"
@ -91,7 +92,7 @@ func (idp *WeiBoIdProvider) GetToken(code string) (*oauth2.Token, error) {
return
}
}(resp.Body)
bs, err := io.ReadAll(resp.Body)
bs, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

View File

@ -22,6 +22,7 @@ import (
"github.com/astaxie/beego/logs"
_ "github.com/astaxie/beego/session/redis"
"github.com/casdoor/casdoor/authz"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/routers"
@ -31,6 +32,7 @@ import (
func main() {
createDatabase := flag.Bool("createDatabase", false, "true if you need casdoor to create database")
flag.Parse()
object.InitAdapter(*createDatabase)
object.InitDb()
object.InitDefaultStorageProvider()
@ -52,12 +54,12 @@ func main() {
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id"
if beego.AppConfig.String("redisEndpoint") == "" {
if conf.GetConfigString("redisEndpoint") == "" {
beego.BConfig.WebConfig.Session.SessionProvider = "file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
} else {
beego.BConfig.WebConfig.Session.SessionProvider = "redis"
beego.BConfig.WebConfig.Session.SessionProviderConfig = beego.AppConfig.String("redisEndpoint")
beego.BConfig.WebConfig.Session.SessionProviderConfig = conf.GetConfigString("redisEndpoint")
}
beego.BConfig.WebConfig.Session.SessionCookieLifeTime = 3600 * 24 * 30
//beego.BConfig.WebConfig.Session.SessionCookieSameSite = http.SameSiteNoneMode

View File

@ -41,7 +41,7 @@ func InitConfig() {
func InitAdapter(createDatabase bool) {
adapter = NewAdapter(beego.AppConfig.String("driverName"), conf.GetBeegoConfDataSourceName(), beego.AppConfig.String("dbName"))
adapter = NewAdapter(conf.GetConfigString("driverName"), conf.GetBeegoConfDataSourceName(), conf.GetConfigString("dbName"))
if createDatabase {
adapter.CreateDatabase()
}
@ -111,10 +111,10 @@ func (a *Adapter) close() {
}
func (a *Adapter) createTable() {
showSql, _ := beego.AppConfig.Bool("showSql")
showSql, _ := conf.GetConfigBool("showSql")
a.Engine.ShowSQL(showSql)
tableNamePrefix := beego.AppConfig.String("tableNamePrefix")
tableNamePrefix := conf.GetConfigString("tableNamePrefix")
tbMapper := core.NewPrefixMapper(core.SnakeMapper{}, tableNamePrefix)
a.Engine.SetTableMapper(tbMapper)

View File

@ -229,7 +229,7 @@ func GetMaskedApplication(application *Application, userId string) *Application
application.OrganizationObj.PasswordSalt = "***"
}
}
return application
return application
}
func GetMaskedApplications(applications []*Application, userId string) []*Application {

View File

@ -19,14 +19,14 @@ import (
"fmt"
"io"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/proxy"
)
var defaultStorageProvider *Provider = nil
func InitDefaultStorageProvider() {
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider")
defaultStorageProviderStr := conf.GetConfigString("defaultStorageProvider")
if defaultStorageProviderStr != "" {
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
}

View File

@ -179,13 +179,14 @@ func CheckUserPassword(organization string, username string, password string) (*
if user.IsForbidden {
return nil, "the user is forbidden to sign in, please contact the administrator"
}
//for ldap users
if user.Ldap != "" {
return checkLdapUserPassword(user, password)
}
msg := CheckPassword(user, password)
if msg != "" {
//for ldap users
if user.Ldap != "" {
return checkLdapUserPassword(user, password)
}
return nil, msg
}

View File

@ -15,17 +15,11 @@
package object
import (
_ "embed"
"io/ioutil"
"github.com/casdoor/casdoor/util"
)
//go:embed token_jwt_key.pem
var tokenJwtPublicKey string
//go:embed token_jwt_key.key
var tokenJwtPrivateKey string
func InitDb() {
initBuiltInOrganization()
initBuiltInUser()
@ -47,9 +41,10 @@ func initBuiltInOrganization() {
DisplayName: "Built-in Organization",
WebsiteUrl: "https://example.com",
Favicon: "https://cdn.casbin.com/static/favicon.ico",
PasswordType: "plain",
PhonePrefix: "86",
DefaultAvatar: "https://casbin.org/img/casbin.svg",
PasswordType: "plain",
Tags: []string{},
}
AddOrganization(organization)
}
@ -121,7 +116,22 @@ func initBuiltInApplication() {
AddApplication(application)
}
func readTokenFromFile() (string, string) {
pemPath := "./object/token_jwt_key.pem"
keyPath := "./object/token_jwt_key.key"
pem, err := ioutil.ReadFile(pemPath)
if err != nil {
return "", ""
}
key, err := ioutil.ReadFile(keyPath)
if err != nil {
return "", ""
}
return string(pem), string(key)
}
func initBuiltInCert() {
tokenJwtPublicKey, tokenJwtPrivateKey := readTokenFromFile()
cert := getCert("admin", "cert-built-in")
if cert != nil {
return

View File

@ -20,7 +20,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"gopkg.in/square/go-jose.v2"
)
@ -58,7 +58,7 @@ func getOriginFromHost(host string) (string, string) {
func GetOidcDiscovery(host string) OidcDiscovery {
originFrontend, originBackend := getOriginFromHost(host)
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
if origin != "" {
originFrontend = origin
originBackend = origin

View File

@ -25,15 +25,16 @@ type Organization struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PhonePrefix string `xorm:"varchar(10)" json:"phonePrefix"`
DefaultAvatar string `xorm:"varchar(100)" json:"defaultAvatar"`
Tags []string `xorm:"mediumtext" json:"tags"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
}
func GetOrganizationCount(owner, field, value string) int {

View File

@ -16,10 +16,9 @@ package object
import (
"fmt"
"net/http"
"github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
"xorm.io/core"
)
@ -29,16 +28,22 @@ type Payment struct {
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
ProductId string `xorm:"varchar(100)" json:"productId"`
ProductName string `xorm:"varchar(100)" json:"productName"`
Price float64 `json:"price"`
Currency string `xorm:"varchar(100)" json:"currency"`
Provider string `xorm:"varchar(100)" json:"provider"`
Type string `xorm:"varchar(100)" json:"type"`
Organization string `xorm:"varchar(100)" json:"organization"`
User string `xorm:"varchar(100)" json:"user"`
ProductName string `xorm:"varchar(100)" json:"productName"`
ProductDisplayName string `xorm:"varchar(100)" json:"productDisplayName"`
State string `xorm:"varchar(100)" json:"state"`
Detail string `xorm:"varchar(100)" json:"detail"`
Tag string `xorm:"varchar(100)" json:"tag"`
Currency string `xorm:"varchar(100)" json:"currency"`
Price float64 `json:"price"`
PayUrl string `xorm:"varchar(2000)" json:"payUrl"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"`
Message string `xorm:"varchar(1000)" json:"message"`
}
func GetPaymentCount(owner, field, value string) int {
@ -61,6 +66,16 @@ func GetPayments(owner string) []*Payment {
return payments
}
func GetUserPayments(owner string, organization string, user string) []*Payment {
payments := []*Payment{}
err := adapter.Engine.Desc("created_time").Find(&payments, &Payment{Owner: owner, Organization: organization, User: user})
if err != nil {
panic(err)
}
return payments
}
func GetPaginationPayments(owner string, offset, limit int, field, value, sortField, sortOrder string) []*Payment {
payments := []*Payment{}
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
@ -127,63 +142,59 @@ func DeletePayment(payment *Payment) bool {
return affected != 0
}
func NotifyPayment(bm gopay.BodyMap) bool {
owner := "admin"
productName := bm.Get("subject")
paymentId := bm.Get("out_trade_no")
priceString := bm.Get("total_amount")
price := util.ParseFloat(priceString)
productId := bm.Get("productId")
providerId := bm.Get("providerId")
product := getProduct(owner, productId)
if product == nil {
panic(fmt.Errorf("the product: %s does not exist", productId))
func notifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) (*Payment, error) {
payment := getPayment(owner, paymentName)
if payment == nil {
return nil, fmt.Errorf("the payment: %s does not exist", paymentName)
}
if productName != product.DisplayName {
panic(fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productName, product.DisplayName))
product := getProduct(owner, productName)
if product == nil {
return nil, fmt.Errorf("the product: %s does not exist", productName)
}
provider, err := product.getProvider(providerName)
if err != nil {
return payment, err
}
pProvider, cert, err := provider.getPaymentProvider()
if err != nil {
return payment, err
}
productDisplayName, paymentName, price, productName, providerName, err := pProvider.Notify(request, body, cert.AuthorityPublicKey)
if err != nil {
return payment, err
}
if productDisplayName != "" && productDisplayName != product.DisplayName {
return nil, fmt.Errorf("the payment's product name: %s doesn't equal to the expected product name: %s", productDisplayName, product.DisplayName)
}
if price != product.Price {
panic(fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price))
return nil, fmt.Errorf("the payment's price: %f doesn't equal to the expected price: %f", price, product.Price)
}
payment := getPayment(owner, paymentId)
if payment == nil {
panic(fmt.Errorf("the payment: %s does not exist", paymentId))
}
return payment, nil
}
provider, err := product.getProvider(providerId)
if err != nil {
panic(err)
}
func NotifyPayment(request *http.Request, body []byte, owner string, providerName string, productName string, paymentName string) bool {
payment, err := notifyPayment(request, body, owner, providerName, productName, paymentName)
cert := getCert(owner, provider.Cert)
if cert == nil {
panic(fmt.Errorf("the cert: %s does not exist", provider.Cert))
}
ok, err := alipay.VerifySignWithCert(cert.AuthorityPublicKey, bm)
if err != nil {
panic(err)
}
if ok {
payment.State = "Paid"
} else {
if cert == nil {
panic(fmt.Errorf("VerifySignWithCert() failed: %v", ok))
if payment != nil {
if err != nil {
payment.State = "Error"
payment.Message = err.Error()
} else {
payment.State = "Paid"
}
//payment.State = "Failed"
UpdatePayment(payment.GetId(), payment)
}
affected, err := adapter.Engine.ID(core.PK{owner, paymentId}).AllCols().Update(payment)
if err != nil {
panic(err)
}
return affected != 0
ok := err == nil
return ok
}
func (payment *Payment) GetId() string {

View File

@ -17,7 +17,6 @@ package object
import (
"fmt"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -36,6 +35,7 @@ type Product struct {
Quantity int `json:"quantity"`
Sold int `json:"sold"`
Providers []string `xorm:"varchar(100)" json:"providers"`
ReturnUrl string `xorm:"varchar(1000)" json:"returnUrl"`
State string `xorm:"varchar(100)" json:"state"`
}
@ -152,33 +152,59 @@ func (product *Product) getProvider(providerId string) (*Provider, error) {
return provider, nil
}
func BuyProduct(id string, providerId string, host string) (string, error) {
func BuyProduct(id string, providerName string, user *User, host string) (string, error) {
product := GetProduct(id)
if product == nil {
return "", fmt.Errorf("the product: %s does not exist", id)
}
provider, err := product.getProvider(providerId)
provider, err := product.getProvider(providerName)
if err != nil {
return "", err
}
cert := getCert(product.Owner, provider.Cert)
if cert == nil {
return "", fmt.Errorf("the cert: %s does not exist", provider.Cert)
pProvider, _, err := provider.getPaymentProvider()
if err != nil {
return "", err
}
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
if pProvider == nil {
return "", fmt.Errorf("the payment provider type: %s is not supported", provider.Type)
}
paymentId := util.GenerateTimeId()
owner := product.Owner
productName := product.Name
paymentName := util.GenerateTimeId()
productDisplayName := product.DisplayName
originFrontend, originBackend := getOriginFromHost(host)
returnUrl := fmt.Sprintf("%s/payments/%s", originFrontend, paymentId)
notifyUrl := fmt.Sprintf("%s/api/notify-payment", originBackend)
returnUrl := fmt.Sprintf("%s/payments/%s/result", originFrontend, paymentName)
notifyUrl := fmt.Sprintf("%s/api/notify-payment/%s/%s/%s/%s", originBackend, owner, providerName, productName, paymentName)
payUrl, err := pProvider.Pay(providerName, productName, paymentName, productDisplayName, product.Price, returnUrl, notifyUrl)
if err != nil {
return "", err
}
payment := Payment{
Owner: product.Owner,
Name: paymentName,
CreatedTime: util.GetCurrentTime(),
DisplayName: paymentName,
Provider: provider.Name,
Type: provider.Type,
Organization: user.Owner,
User: user.Name,
ProductName: productName,
ProductDisplayName: productDisplayName,
Detail: product.Detail,
Tag: product.Tag,
Currency: product.Currency,
Price: product.Price,
PayUrl: payUrl,
ReturnUrl: product.ReturnUrl,
State: "Created",
}
affected := AddPayment(&payment)
if !affected {
return "", fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
}
payUrl, err := pProvider.Pay(product.DisplayName, product.Name, provider.Name, paymentId, product.Price, returnUrl, notifyUrl)
return payUrl, err
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
//go:build !skipCi
// +build !skipCi
package object
@ -29,7 +30,7 @@ func TestProduct(t *testing.T) {
product := GetProduct("admin/product_123")
provider := getProvider(product.Owner, "provider_pay_alipay")
cert := getCert(product.Owner, "cert-pay-alipay")
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
pProvider := pp.GetPaymentProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
paymentId := util.GenerateTimeId()
returnUrl := ""

View File

@ -17,6 +17,7 @@ package object
import (
"fmt"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -182,6 +183,23 @@ func DeleteProvider(provider *Provider) bool {
return affected != 0
}
func (p *Provider) getPaymentProvider() (pp.PaymentProvider, *Cert, error) {
cert := &Cert{}
if p.Cert != "" {
cert = getCert(p.Owner, p.Cert)
if cert == nil {
return nil, nil, fmt.Errorf("the cert: %s does not exist", p.Cert)
}
}
pProvider := pp.GetPaymentProvider(p.Type, p.ClientId, p.ClientSecret, p.Host, cert.PublicKey, cert.PrivateKey, cert.AuthorityPublicKey, cert.AuthorityRootPublicKey)
if pProvider == nil {
return nil, cert, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
}
return pProvider, cert, nil
}
func (p *Provider) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}

View File

@ -34,6 +34,9 @@ func (application *Application) GetProviderItem(providerName string) *ProviderIt
}
func (pi *ProviderItem) IsProviderVisible() bool {
if pi.Provider == nil {
return false
}
return pi.Provider.Category == "OAuth" || pi.Provider.Category == "SAML"
}

View File

@ -18,8 +18,8 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/astaxie/beego/context"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
)
@ -27,7 +27,7 @@ var logPostOnly bool
func init() {
var err error
logPostOnly, err = beego.AppConfig.Bool("logPostOnly")
logPostOnly, err = conf.GetConfigBool("logPostOnly")
if err != nil {
//panic(err)
}

View File

@ -23,7 +23,7 @@ import (
type Resource struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
Name string `xorm:"varchar(200) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
User string `xorm:"varchar(100)" json:"user"`

View File

@ -23,7 +23,7 @@ import (
"regexp"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
saml2 "github.com/russellhaering/gosaml2"
dsig "github.com/russellhaering/goxmldsig"
)
@ -73,7 +73,7 @@ func buildSp(provider *Provider, samlResponse string) (*saml2.SAMLServiceProvide
certStore := dsig.MemoryX509CertificateStore{
Roots: []*x509.Certificate{},
}
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
certEncodedData := ""
if samlResponse != "" {
certEncodedData = parseSamlResponse(samlResponse, provider.Type)

View File

@ -18,6 +18,9 @@ import "github.com/casdoor/go-sms-sender"
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.AppId)
if provider.Type == go_sms_sender.HuaweiCloud {
client, err = go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.TemplateCode, provider.ProviderUrl, provider.AppId)
}
if err != nil {
return err
}

View File

@ -19,7 +19,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/storage"
"github.com/casdoor/casdoor/util"
)
@ -28,7 +28,7 @@ var isCloudIntranet bool
func init() {
var err error
isCloudIntranet, err = beego.AppConfig.Bool("isCloudIntranet")
isCloudIntranet, err = conf.GetConfigBool("isCloudIntranet")
if err != nil {
//panic(err)
}

View File

@ -10,6 +10,7 @@
// 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.
//go:build !skipCi
// +build !skipCi

View File

@ -439,6 +439,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
TokenType: "Bearer",
}
AddToken(newToken)
DeleteToken(&token)
tokenWrapper := &TokenWrapper{
AccessToken: token.AccessToken,

View File

@ -15,11 +15,10 @@
package object
import (
_ "embed"
"fmt"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/golang-jwt/jwt/v4"
)
@ -67,7 +66,7 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours) * time.Hour)
user.Password = ""
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
_, originBackend := getOriginFromHost(host)
if origin != "" {
originBackend = origin

View File

@ -18,7 +18,7 @@ import (
"fmt"
"strings"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -85,6 +85,7 @@ type User struct {
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
Adfs string `xorm:"adfs varchar(100)" json:"adfs"`
Baidu string `xorm:"baidu varchar(100)" json:"baidu"`
Casdoor string `xorm:"casdoor varchar(100)" json:"casdoor"`
Infoflow string `xorm:"infoflow varchar(100)" json:"infoflow"`
Apple string `xorm:"apple varchar(100)" json:"apple"`
AzureAD string `xorm:"azuread varchar(100)" json:"azuread"`
@ -380,7 +381,9 @@ func AddUsers(users []*User) bool {
affected, err := adapter.Engine.Insert(users)
if err != nil {
panic(err)
if !strings.Contains(err.Error(), "Duplicate entry") {
panic(err)
}
}
return affected != 0
@ -426,7 +429,7 @@ func GetUserInfo(userId string, scope string, aud string, host string) (*Userinf
if user == nil {
return nil, fmt.Errorf("the user: %s doesn't exist", userId)
}
origin := beego.AppConfig.String("origin")
origin := conf.GetConfigString("origin")
_, originBackend := getOriginFromHost(host)
if origin != "" {
originBackend = origin

View File

@ -52,8 +52,8 @@ func UploadUsers(owner string, fileId string) bool {
oldUserMap := getUserMap(owner)
newUsers := []*User{}
for _, line := range table {
if parseLineItem(&line, 0) == "" {
for index, line := range table {
if index == 0 || parseLineItem(&line, 0) == "" {
continue
}
@ -67,38 +67,42 @@ func UploadUsers(owner string, fileId string) bool {
Password: parseLineItem(&line, 6),
PasswordSalt: parseLineItem(&line, 7),
DisplayName: parseLineItem(&line, 8),
Avatar: parseLineItem(&line, 9),
FirstName: parseLineItem(&line, 9),
LastName: parseLineItem(&line, 10),
Avatar: parseLineItem(&line, 11),
PermanentAvatar: "",
Email: parseLineItem(&line, 10),
Phone: parseLineItem(&line, 11),
Location: parseLineItem(&line, 12),
Address: []string{parseLineItem(&line, 13)},
Affiliation: parseLineItem(&line, 14),
Title: parseLineItem(&line, 15),
IdCardType: parseLineItem(&line, 16),
IdCard: parseLineItem(&line, 17),
Homepage: parseLineItem(&line, 18),
Bio: parseLineItem(&line, 19),
Tag: parseLineItem(&line, 20),
Region: parseLineItem(&line, 21),
Language: parseLineItem(&line, 22),
Gender: parseLineItem(&line, 23),
Birthday: parseLineItem(&line, 24),
Education: parseLineItem(&line, 25),
Score: parseLineItemInt(&line, 26),
Ranking: parseLineItemInt(&line, 27),
Email: parseLineItem(&line, 12),
Phone: parseLineItem(&line, 13),
Location: parseLineItem(&line, 14),
Address: []string{parseLineItem(&line, 15)},
Affiliation: parseLineItem(&line, 16),
Title: parseLineItem(&line, 17),
IdCardType: parseLineItem(&line, 18),
IdCard: parseLineItem(&line, 19),
Homepage: parseLineItem(&line, 20),
Bio: parseLineItem(&line, 21),
Tag: parseLineItem(&line, 22),
Region: parseLineItem(&line, 23),
Language: parseLineItem(&line, 24),
Gender: parseLineItem(&line, 25),
Birthday: parseLineItem(&line, 26),
Education: parseLineItem(&line, 27),
Score: parseLineItemInt(&line, 28),
Karma: parseLineItemInt(&line, 29),
Ranking: parseLineItemInt(&line, 30),
IsDefaultAvatar: false,
IsOnline: parseLineItemBool(&line, 28),
IsAdmin: parseLineItemBool(&line, 29),
IsGlobalAdmin: parseLineItemBool(&line, 30),
IsForbidden: parseLineItemBool(&line, 31),
IsDeleted: parseLineItemBool(&line, 32),
SignupApplication: parseLineItem(&line, 33),
IsOnline: parseLineItemBool(&line, 31),
IsAdmin: parseLineItemBool(&line, 32),
IsGlobalAdmin: parseLineItemBool(&line, 33),
IsForbidden: parseLineItemBool(&line, 34),
IsDeleted: parseLineItemBool(&line, 35),
SignupApplication: parseLineItem(&line, 36),
Hash: "",
PreHash: "",
CreatedIp: parseLineItem(&line, 34),
LastSigninTime: parseLineItem(&line, 35),
LastSigninIp: parseLineItem(&line, 36),
CreatedIp: parseLineItem(&line, 37),
LastSigninTime: parseLineItem(&line, 38),
LastSigninIp: parseLineItem(&line, 39),
Ldap: "",
Properties: map[string]string{},
}

View File

@ -20,7 +20,7 @@ import (
"math/rand"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
@ -129,7 +129,7 @@ func CheckVerificationCode(dest, code string) string {
return "Code has not been sent yet!"
}
timeout, err := beego.AppConfig.Int64("verificationCodeTimeout")
timeout, err := conf.GetConfigInt64("verificationCodeTimeout")
if err != nil {
panic(err)
}

View File

@ -17,8 +17,9 @@ package pp
import (
"context"
"fmt"
"strings"
"net/http"
"github.com/casdoor/casdoor/util"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
)
@ -44,20 +45,20 @@ func NewAlipayPaymentProvider(appId string, appPublicKey string, appPrivateKey s
return pp
}
func (pp *AlipayPaymentProvider) Pay(productName string, productId string, providerId string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error) {
pp.Client.DebugSwitch = gopay.DebugOn
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
//pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{}
bm.Set("subject", productName)
bm.Set("out_trade_no", paymentId)
bm.Set("total_amount", priceString)
bm.Set("providerName", providerName)
bm.Set("productName", productName)
bm.Set("return_url", returnUrl)
bm.Set("notify_url", notifyUrl)
bm.Set("productId", productId)
bm.Set("providerId", productId)
bm.Set("subject", productDisplayName)
bm.Set("out_trade_no", paymentName)
bm.Set("total_amount", getPriceString(price))
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil {
@ -65,3 +66,27 @@ func (pp *AlipayPaymentProvider) Pay(productName string, productId string, provi
}
return payUrl, nil
}
func (pp *AlipayPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
bm, err := alipay.ParseNotifyToBodyMap(request)
if err != nil {
return "", "", 0, "", "", err
}
providerName := bm.Get("providerName")
productName := bm.Get("productName")
productDisplayName := bm.Get("subject")
paymentName := bm.Get("out_trade_no")
price := util.ParseFloat(bm.Get("total_amount"))
ok, err := alipay.VerifySignWithCert(authorityPublicKey, bm)
if err != nil {
return "", "", 0, "", "", err
}
if !ok {
return "", "", 0, "", "", fmt.Errorf("VerifySignWithCert() failed: %v", ok)
}
return productDisplayName, paymentName, price, productName, providerName, nil
}

232
pp/gc.go Normal file
View File

@ -0,0 +1,232 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pp
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/casdoor/casdoor/util"
)
type GcPaymentProvider struct {
Xmpch string
SecretKey string
Host string
}
type GcPayReqInfo struct {
OrderDate string `json:"orderdate"`
OrderNo string `json:"orderno"`
Amount string `json:"amount"`
PayerId string `json:"payerid"`
PayerName string `json:"payername"`
Xmpch string `json:"xmpch"`
ReturnUrl string `json:"return_url"`
NotifyUrl string `json:"notify_url"`
}
type GcPayRespInfo struct {
Jylsh string `json:"jylsh"`
Amount string `json:"amount"`
PayerId string `json:"payerid"`
PayerName string `json:"payername"`
PayUrl string `json:"payurl"`
}
type GcNotifyRespInfo struct {
Xmpch string `json:"xmpch"`
OrderDate string `json:"orderdate"`
OrderNo string `json:"orderno"`
Amount float64 `json:"amount"`
Jylsh string `json:"jylsh"`
TradeNo string `json:"tradeno"`
PayMethod string `json:"paymethod"`
OrderState string `json:"orderstate"`
ReturnType string `json:"return_type"`
PayerId string `json:"payerid"`
PayerName string `json:"payername"`
}
type GcRequestBody struct {
Op string `json:"op"`
Xmpch string `json:"xmpch"`
Version string `json:"version"`
Data string `json:"data"`
RequestTime string `json:"requesttime"`
Sign string `json:"sign"`
}
type GcResponseBody struct {
Op string `json:"op"`
Xmpch string `json:"xmpch"`
Version string `json:"version"`
ReturnCode string `json:"return_code"`
ReturnMsg string `json:"return_msg"`
Data string `json:"data"`
NotifyTime string `json:"notifytime"`
Sign string `json:"sign"`
}
func NewGcPaymentProvider(clientId string, clientSecret string, host string) *GcPaymentProvider {
pp := &GcPaymentProvider{}
pp.Xmpch = clientId
pp.SecretKey = clientSecret
pp.Host = host
return pp
}
func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
client := &http.Client{}
var resp *http.Response
var err error
contentType := "text/plain;charset=UTF-8"
body := bytes.NewReader(postBytes)
req, err := http.NewRequest("POST", pp.Host, body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", contentType)
resp, err = client.Do(req)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)
respBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return respBytes, nil
}
func (pp *GcPaymentProvider) Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error) {
payReqInfo := GcPayReqInfo{
OrderDate: util.GenerateSimpleTimeId(),
OrderNo: util.GenerateTimeId(),
Amount: getPriceString(price),
PayerId: "",
PayerName: "",
Xmpch: pp.Xmpch,
ReturnUrl: returnUrl,
NotifyUrl: notifyUrl,
}
b, err := json.Marshal(payReqInfo)
if err != nil {
return "", err
}
body := GcRequestBody{
Op: "OrderCreate",
Xmpch: pp.Xmpch,
Version: "1.4",
Data: base64.StdEncoding.EncodeToString(b),
RequestTime: util.GenerateSimpleTimeId(),
}
params := fmt.Sprintf("data=%s&op=%s&requesttime=%s&version=%s&xmpch=%s%s", body.Data, body.Op, body.RequestTime, body.Version, body.Xmpch, pp.SecretKey)
body.Sign = strings.ToUpper(util.GetMd5Hash(params))
bodyBytes, err := json.Marshal(body)
if err != nil {
return "", err
}
respBytes, err := pp.doPost(bodyBytes)
if err != nil {
return "", err
}
var respBody GcResponseBody
err = json.Unmarshal(respBytes, &respBody)
if err != nil {
return "", err
}
if respBody.ReturnCode != "SUCCESS" {
return "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
}
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
if err != nil {
return "", err
}
var payRespInfo GcPayRespInfo
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
if err != nil {
return "", err
}
return payRespInfo.PayUrl, nil
}
func (pp *GcPaymentProvider) Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error) {
reqBody := GcRequestBody{}
m, err := url.ParseQuery(string(body))
if err != nil {
return "", "", 0, "", "", err
}
reqBody.Op = m["op"][0]
reqBody.Xmpch = m["xmpch"][0]
reqBody.Version = m["version"][0]
reqBody.Data = m["data"][0]
reqBody.RequestTime = m["requesttime"][0]
reqBody.Sign = m["sign"][0]
notifyReqInfoBytes, err := base64.StdEncoding.DecodeString(reqBody.Data)
if err != nil {
return "", "", 0, "", "", err
}
var notifyRespInfo GcNotifyRespInfo
err = json.Unmarshal(notifyReqInfoBytes, &notifyRespInfo)
if err != nil {
return "", "", 0, "", "", err
}
providerName := ""
productName := ""
productDisplayName := ""
paymentName := notifyRespInfo.OrderNo
price := notifyRespInfo.Amount
if notifyRespInfo.OrderState != "1" {
return "", "", 0, "", "", fmt.Errorf("error order state: %s", notifyRespInfo.OrderDate)
}
return productDisplayName, paymentName, price, productName, providerName, nil
}

View File

@ -14,13 +14,18 @@
package pp
import "net/http"
type PaymentProvider interface {
Pay(productName string, productId string, providerId string, paymentId string, price float64, returnUrl string, notifyUrl string) (string, error)
Pay(providerName string, productName string, paymentName string, productDisplayName string, price float64, returnUrl string, notifyUrl string) (string, error)
Notify(request *http.Request, body []byte, authorityPublicKey string) (string, string, float64, string, string, error)
}
func GetPaymentProvider(typ string, appId string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
func GetPaymentProvider(typ string, appId string, clientSecret string, host string, appPublicKey string, appPrivateKey string, authorityPublicKey string, authorityRootPublicKey string) PaymentProvider {
if typ == "Alipay" {
return NewAlipayPaymentProvider(appId, appPublicKey, appPrivateKey, authorityPublicKey, authorityRootPublicKey)
} else if typ == "GC" {
return NewGcPaymentProvider(appId, clientSecret, host)
}
return nil
}

25
pp/util.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pp
import (
"fmt"
"strings"
)
func getPriceString(price float64) string {
priceString := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.2f", price), "0"), ".")
return priceString
}

View File

@ -21,7 +21,7 @@ import (
"strings"
"time"
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/conf"
"golang.org/x/net/proxy"
)
@ -54,7 +54,7 @@ func isAddressOpen(address string) bool {
}
func getProxyHttpClient() *http.Client {
sock5Proxy := beego.AppConfig.String("sock5Proxy")
sock5Proxy := conf.GetConfigString("sock5Proxy")
if sock5Proxy == "" {
return &http.Client{}
}

View File

@ -84,8 +84,8 @@ func getObject(ctx *context.Context) (string, string) {
if path == "/api/delete-resource" {
tokens := strings.Split(obj.Name, "/")
if len(tokens) >= 2 {
obj.Name = tokens[len(tokens)-2]
if len(tokens) >= 5 {
obj.Name = tokens[4]
}
}

View File

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

View File

@ -159,11 +159,12 @@ func initAPI() {
beego.Router("/api/buy-product", &controllers.ApiController{}, "POST:BuyProduct")
beego.Router("/api/get-payments", &controllers.ApiController{}, "GET:GetPayments")
beego.Router("/api/get-user-payments", &controllers.ApiController{}, "GET:GetUserPayments")
beego.Router("/api/get-payment", &controllers.ApiController{}, "GET:GetPayment")
beego.Router("/api/update-payment", &controllers.ApiController{}, "POST:UpdatePayment")
beego.Router("/api/add-payment", &controllers.ApiController{}, "POST:AddPayment")
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
beego.Router("/api/notify-payment", &controllers.ApiController{}, "POST:NotifyPayment")
beego.Router("/api/notify-payment/?:owner/?:provider/?:product/?:payment", &controllers.ApiController{}, "POST:NotifyPayment")
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")

View File

@ -478,6 +478,39 @@
}
}
},
"/api/buy-product": {
"post": {
"tags": [
"Product API"
],
"description": "buy product",
"operationId": "ApiController.BuyProduct",
"parameters": [
{
"in": "query",
"name": "id",
"description": "The id of the product",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "providerName",
"description": "The name of the provider",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/check-ldap-users-exist": {
"post": {
"tags": [
@ -1710,6 +1743,49 @@
}
}
},
"/api/get-user-payments": {
"get": {
"tags": [
"Payment API"
],
"description": "get payments for a user",
"operationId": "ApiController.GetUserPayments",
"parameters": [
{
"in": "query",
"name": "owner",
"description": "The owner of payments",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "organization",
"description": "The organization of the user",
"required": true,
"type": "string"
},
{
"in": "query",
"name": "user",
"description": "The username of the user",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/object.Payment"
}
}
}
}
}
},
"/api/get-users": {
"get": {
"tags": [
@ -1936,6 +2012,36 @@
}
}
},
"/api/login/oauth/introspect": {
"post": {
"description": "The introspection endpoint is an OAuth 2.0 endpoint that takes a",
"operationId": "ApiController.IntrospectToken",
"parameters": [
{
"in": "formData",
"name": "token",
"description": "access_token's value or refresh_token's value",
"required": true,
"type": "string"
},
{
"in": "formData",
"name": "token_type_hint",
"description": "the token type access_token or refresh_token",
"required": true,
"type": "string"
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/object.IntrospectionResponse"
}
}
}
}
},
"/api/login/oauth/logout": {
"get": {
"tags": [
@ -2015,7 +2121,6 @@
"in": "query",
"name": "client_secret",
"description": "OAuth client secret",
"required": true,
"type": "string"
}
],
@ -2046,6 +2151,34 @@
}
}
},
"/api/notify-payment": {
"post": {
"tags": [
"Payment API"
],
"description": "notify payment",
"operationId": "ApiController.NotifyPayment",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The details of the payment",
"required": true,
"schema": {
"$ref": "#/definitions/object.Payment"
}
}
],
"responses": {
"200": {
"description": "The Response object",
"schema": {
"$ref": "#/definitions/controllers.Response"
}
}
}
}
},
"/api/send-verification-code": {
"post": {
"tags": [
@ -2664,11 +2797,11 @@
}
},
"definitions": {
"2015.0xc0000edb90.false": {
"2026.0xc000380de0.false": {
"title": "false",
"type": "object"
},
"2049.0xc0000edbc0.false": {
"2060.0xc000380e10.false": {
"title": "false",
"type": "object"
},
@ -2685,10 +2818,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/2015.0xc0000edb90.false"
"$ref": "#/definitions/2026.0xc000380de0.false"
},
"data2": {
"$ref": "#/definitions/2049.0xc0000edbc0.false"
"$ref": "#/definitions/2060.0xc000380e10.false"
},
"msg": {
"type": "string"
@ -2709,10 +2842,10 @@
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/2015.0xc0000edb90.false"
"$ref": "#/definitions/2026.0xc000380de0.false"
},
"data2": {
"$ref": "#/definitions/2049.0xc0000edbc0.false"
"$ref": "#/definitions/2060.0xc000380e10.false"
},
"msg": {
"type": "string"
@ -2864,6 +2997,12 @@
"title": "Cert",
"type": "object",
"properties": {
"authorityPublicKey": {
"type": "string"
},
"authorityRootPublicKey": {
"type": "string"
},
"bitSize": {
"type": "integer",
"format": "int64"
@ -2913,6 +3052,54 @@
}
}
},
"object.IntrospectionResponse": {
"title": "IntrospectionResponse",
"type": "object",
"properties": {
"active": {
"type": "boolean"
},
"aud": {
"type": "array",
"items": {
"type": "string"
}
},
"client_id": {
"type": "string"
},
"exp": {
"type": "integer",
"format": "int64"
},
"iat": {
"type": "integer",
"format": "int64"
},
"iss": {
"type": "string"
},
"jti": {
"type": "string"
},
"nbf": {
"type": "integer",
"format": "int64"
},
"scope": {
"type": "string"
},
"sub": {
"type": "string"
},
"token_type": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"object.Organization": {
"title": "Organization",
"type": "object",
@ -2950,6 +3137,12 @@
"phonePrefix": {
"type": "string"
},
"tags": {
"type": "array",
"items": {
"type": "string"
}
},
"websiteUrl": {
"type": "string"
}
@ -2959,19 +3152,19 @@
"title": "Payment",
"type": "object",
"properties": {
"amount": {
"type": "string"
},
"createdTime": {
"type": "string"
},
"currency": {
"type": "string"
},
"detail": {
"type": "string"
},
"displayName": {
"type": "string"
},
"good": {
"message": {
"type": "string"
},
"name": {
@ -2983,12 +3176,31 @@
"owner": {
"type": "string"
},
"payUrl": {
"type": "string"
},
"price": {
"type": "number",
"format": "double"
},
"productDisplayName": {
"type": "string"
},
"productName": {
"type": "string"
},
"provider": {
"type": "string"
},
"returnUrl": {
"type": "string"
},
"state": {
"type": "string"
},
"tag": {
"type": "string"
},
"type": {
"type": "string"
},
@ -3074,8 +3286,8 @@
"type": "string"
},
"price": {
"type": "integer",
"format": "int64"
"type": "number",
"format": "double"
},
"providers": {
"type": "array",
@ -3087,6 +3299,9 @@
"type": "integer",
"format": "int64"
},
"returnUrl": {
"type": "string"
},
"sold": {
"type": "integer",
"format": "int64"
@ -3112,6 +3327,9 @@
"category": {
"type": "string"
},
"cert": {
"type": "string"
},
"clientId": {
"type": "string"
},
@ -3482,6 +3700,9 @@
"birthday": {
"type": "string"
},
"casdoor": {
"type": "string"
},
"createdIp": {
"type": "string"
},

View File

@ -309,6 +309,28 @@ paths:
description: object
schema:
$ref: '#/definitions/Response'
/api/buy-product:
post:
tags:
- Product API
description: buy product
operationId: ApiController.BuyProduct
parameters:
- in: query
name: id
description: The id of the product
required: true
type: string
- in: query
name: providerName
description: The name of the provider
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/check-ldap-users-exist:
post:
tags:
@ -1111,6 +1133,35 @@ paths:
responses:
"200":
description: '{int} int The count of filtered users for an organization'
/api/get-user-payments:
get:
tags:
- Payment API
description: get payments for a user
operationId: ApiController.GetUserPayments
parameters:
- in: query
name: owner
description: The owner of payments
required: true
type: string
- in: query
name: organization
description: The organization of the user
required: true
type: string
- in: query
name: user
description: The username of the user
required: true
type: string
responses:
"200":
description: The Response object
schema:
type: array
items:
$ref: '#/definitions/object.Payment'
/api/get-users:
get:
tags:
@ -1262,6 +1313,26 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/object.TokenWrapper'
/api/login/oauth/introspect:
post:
description: The introspection endpoint is an OAuth 2.0 endpoint that takes a
operationId: ApiController.IntrospectToken
parameters:
- in: formData
name: token
description: access_token's value or refresh_token's value
required: true
type: string
- in: formData
name: token_type_hint
description: the token type access_token or refresh_token
required: true
type: string
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/object.IntrospectionResponse'
/api/login/oauth/logout:
get:
tags:
@ -1318,7 +1389,6 @@ paths:
- in: query
name: client_secret
description: OAuth client secret
required: true
type: string
responses:
"200":
@ -1336,6 +1406,24 @@ paths:
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/notify-payment:
post:
tags:
- Payment API
description: notify payment
operationId: ApiController.NotifyPayment
parameters:
- in: body
name: body
description: The details of the payment
required: true
schema:
$ref: '#/definitions/object.Payment'
responses:
"200":
description: The Response object
schema:
$ref: '#/definitions/controllers.Response'
/api/send-verification-code:
post:
tags:
@ -1743,10 +1831,10 @@ paths:
schema:
$ref: '#/definitions/object.Userinfo'
definitions:
2015.0xc0000edb90.false:
2026.0xc000380de0.false:
title: "false"
type: object
2049.0xc0000edbc0.false:
2060.0xc000380e10.false:
title: "false"
type: object
RequestForm:
@ -1760,9 +1848,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/2015.0xc0000edb90.false'
$ref: '#/definitions/2026.0xc000380de0.false'
data2:
$ref: '#/definitions/2049.0xc0000edbc0.false'
$ref: '#/definitions/2060.0xc000380e10.false'
msg:
type: string
name:
@ -1776,9 +1864,9 @@ definitions:
type: object
properties:
data:
$ref: '#/definitions/2015.0xc0000edb90.false'
$ref: '#/definitions/2026.0xc000380de0.false'
data2:
$ref: '#/definitions/2049.0xc0000edbc0.false'
$ref: '#/definitions/2060.0xc000380e10.false'
msg:
type: string
name:
@ -1880,6 +1968,10 @@ definitions:
title: Cert
type: object
properties:
authorityPublicKey:
type: string
authorityRootPublicKey:
type: string
bitSize:
type: integer
format: int64
@ -1912,6 +2004,39 @@ definitions:
type: string
value:
type: string
object.IntrospectionResponse:
title: IntrospectionResponse
type: object
properties:
active:
type: boolean
aud:
type: array
items:
type: string
client_id:
type: string
exp:
type: integer
format: int64
iat:
type: integer
format: int64
iss:
type: string
jti:
type: string
nbf:
type: integer
format: int64
scope:
type: string
sub:
type: string
token_type:
type: string
username:
type: string
object.Organization:
title: Organization
type: object
@ -1938,21 +2063,25 @@ definitions:
type: string
phonePrefix:
type: string
tags:
type: array
items:
type: string
websiteUrl:
type: string
object.Payment:
title: Payment
type: object
properties:
amount:
type: string
createdTime:
type: string
currency:
type: string
detail:
type: string
displayName:
type: string
good:
message:
type: string
name:
type: string
@ -1960,10 +2089,23 @@ definitions:
type: string
owner:
type: string
payUrl:
type: string
price:
type: number
format: double
productDisplayName:
type: string
productName:
type: string
provider:
type: string
returnUrl:
type: string
state:
type: string
tag:
type: string
type:
type: string
user:
@ -2021,8 +2163,8 @@ definitions:
owner:
type: string
price:
type: integer
format: int64
type: number
format: double
providers:
type: array
items:
@ -2030,6 +2172,8 @@ definitions:
quantity:
type: integer
format: int64
returnUrl:
type: string
sold:
type: integer
format: int64
@ -2047,6 +2191,8 @@ definitions:
type: string
category:
type: string
cert:
type: string
clientId:
type: string
clientId2:
@ -2296,6 +2442,8 @@ definitions:
type: string
birthday:
type: string
casdoor:
type: string
createdIp:
type: string
createdTime:

View File

@ -20,10 +20,12 @@ import (
)
var rePhoneCn *regexp.Regexp
var rePhone *regexp.Regexp
func init() {
// https://learnku.com/articles/31543
rePhoneCn, _ = regexp.Compile(`^1(3\d|4[5-9]|5[0-35-9]|6[2567]|7[0-8]|8\d|9[0-35-9])\d{8}$`)
rePhone, _ = regexp.Compile("(\\d{3})\\d*(\\d{4})")
}
func IsEmailValid(email string) bool {
@ -34,3 +36,7 @@ func IsEmailValid(email string) bool {
func IsPhoneCnValid(phone string) bool {
return rePhoneCn.MatchString(phone)
}
func getMaskedPhone(phone string) string {
return rePhone.ReplaceAllString(phone, "$1****$2")
}

View File

@ -20,7 +20,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"os"
"io/ioutil"
"strconv"
"strings"
"time"
@ -109,6 +109,14 @@ func GenerateTimeId() string {
return res
}
func GenerateSimpleTimeId() string {
timestamp := time.Now().Unix()
tm := time.Unix(timestamp, 0)
t := tm.Format("20060102150405")
return t
}
func GetId(name string) string {
return fmt.Sprintf("admin/%s", name)
}
@ -154,7 +162,7 @@ func GetMinLenStr(strs ...string) string {
}
func ReadStringFromPath(path string) string {
data, err := os.ReadFile(path)
data, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
}
@ -163,7 +171,7 @@ func ReadStringFromPath(path string) string {
}
func WriteStringToPath(s string, path string) {
err := os.WriteFile(path, []byte(s), 0644)
err := ioutil.WriteFile(path, []byte(s), 0644)
if err != nil {
panic(err)
}
@ -198,3 +206,28 @@ func IsChinese(str string) bool {
}
return flag
}
func GetMaskedPhone(phone string) string {
return getMaskedPhone(phone)
}
func GetMaskedEmail(email string) string {
if email == "" {
return ""
}
tokens := strings.Split(email, "@")
username := maskString(tokens[0])
domain := tokens[1]
domainTokens := strings.Split(domain, ".")
domainTokens[len(domainTokens)-2] = maskString(domainTokens[len(domainTokens)-2])
return fmt.Sprintf("%s@%s", username, strings.Join(domainTokens, "."))
}
func maskString(str string) string {
if len(str) <= 2 {
return str
} else {
return fmt.Sprintf("%c%s%c", str[0], strings.Repeat("*", len(str)-2), str[len(str)-1])
}
}

View File

@ -245,3 +245,4 @@ func TestSnakeString(t *testing.T) {
})
}
}

View File

@ -48,6 +48,7 @@ import ProductEditPage from "./ProductEditPage";
import ProductBuyPage from "./ProductBuyPage";
import PaymentListPage from "./PaymentListPage";
import PaymentEditPage from "./PaymentEditPage";
import PaymentResultPage from "./PaymentResultPage";
import AccountPage from "./account/AccountPage";
import HomePage from "./basic/HomePage";
import CustomGithubCorner from "./CustomGithubCorner";
@ -234,8 +235,12 @@ class App extends Component {
});
Setting.showMessage("success", `Logged out successfully`);
Setting.goToLinkSoft(this, "/");
let redirectUri = res.data2;
if (redirectUri !== null && redirectUri !== undefined && redirectUri !== "") {
Setting.goToLink(redirectUri);
}else{
Setting.goToLinkSoft(this, "/");
}
} else {
Setting.showMessage("error", `Failed to log out: ${res.msg}`);
}
@ -529,6 +534,7 @@ class App extends Component {
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)}/>
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)}/>
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)}/>
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)}/>
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />}/>
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
@ -551,22 +557,22 @@ class App extends Component {
</Link>
)
}
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: '64px', width: '100%', position: 'absolute'}}
>
<div>
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: '64px', width: '80%', position: 'absolute'}}
>
{
this.renderMenu()
}
</Menu>
{
this.renderMenu()
this.renderAccount()
}
<div style = {{float: 'right'}}>
{
this.renderAccount()
}
<SelectLanguageBox/>
</div>
</Menu>
<SelectLanguageBox/>
</div>
</Header>
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
<Card className="content-warp-card">

View File

@ -113,7 +113,7 @@ class OrganizationEditPage extends React.Component {
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel("general:Favicon", i18next.t("general:Favicon - Tooltip"))} :
{Setting.getLabel( i18next.t("general:Favicon"), i18next.t("general:Favicon - Tooltip"))} :
</Col>
<Col span={22} >
<Row style={{marginTop: '20px'}} >
@ -208,6 +208,18 @@ class OrganizationEditPage extends React.Component {
</Row>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Tags"), i18next.t("organization:Tags - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} mode="tags" style={{width: '100%'}} value={this.state.organization.tags} onChange={(value => {this.updateOrganizationField('tags', value);})}>
{
this.state.organization.tags?.map((item, index) => <Option key={index} value={item}>{item}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Master password"), i18next.t("general:Master password - Tooltip"))} :

View File

@ -36,6 +36,7 @@ class OrganizationListPage extends BaseListPage {
PasswordSalt: "",
phonePrefix: "86",
defaultAvatar: "https://casbin.org/img/casbin.svg",
tags: [],
masterPassword: "",
enableSoftDeletion: false,
}

View File

@ -150,6 +150,26 @@ class PaymentEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("payment:State"), i18next.t("payment:State - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.payment.state} onChange={e => {
// this.updatePaymentField('state', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("payment:Message"), i18next.t("payment:Message - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.payment.message} onChange={e => {
// this.updatePaymentField('message', e.target.value);
}} />
</Col>
</Row>
</Card>
)
}

View File

@ -34,11 +34,16 @@ class PaymentListPage extends BaseListPage {
type: "PayPal",
organization: "built-in",
user: "admin",
productId: "computer-1",
productName: "A notebook computer",
price: 300.00,
productName: "computer-1",
productDisplayName: "A notebook computer",
detail: "This is a computer with excellent CPU, memory and disk",
tag: "Promotion-1",
currency: "USD",
price: 300.00,
payUrl: "https://pay.com/pay.php",
returnUrl: "https://door.casdoor.com/payments",
state: "Paid",
message: "",
}
}
@ -73,11 +78,11 @@ class PaymentListPage extends BaseListPage {
const columns = [
{
title: i18next.t("general:Organization"),
dataIndex: 'owner',
key: 'owner',
dataIndex: 'organization',
key: 'organization',
width: '120px',
sorter: true,
...this.getColumnSearchProps('owner'),
...this.getColumnSearchProps('organization'),
render: (text, record, index) => {
return (
<Link to={`/organizations/${text}`}>
@ -105,7 +110,7 @@ class PaymentListPage extends BaseListPage {
title: i18next.t("general:Name"),
dataIndex: 'name',
key: 'name',
width: '150px',
width: '180px',
fixed: 'left',
sorter: true,
...this.getColumnSearchProps('name'),
@ -155,7 +160,7 @@ class PaymentListPage extends BaseListPage {
title: i18next.t("payment:Type"),
dataIndex: 'type',
key: 'type',
width: '110px',
width: '140px',
align: 'center',
filterMultiple: false,
filters: Setting.getProviderTypeOptions('Payment').map((o) => {return {text:o.id, value:o.name}}),
@ -167,11 +172,11 @@ class PaymentListPage extends BaseListPage {
},
{
title: i18next.t("payment:Product"),
dataIndex: 'productName',
key: 'productName',
width: '160px',
dataIndex: 'productDisplayName',
key: 'productDisplayName',
// width: '160px',
sorter: true,
...this.getColumnSearchProps('productName'),
...this.getColumnSearchProps('productDisplayName'),
},
{
title: i18next.t("payment:Price"),
@ -189,15 +194,24 @@ class PaymentListPage extends BaseListPage {
sorter: true,
...this.getColumnSearchProps('currency'),
},
{
title: i18next.t("payment:State"),
dataIndex: 'state',
key: 'state',
width: '120px',
sorter: true,
...this.getColumnSearchProps('state'),
},
{
title: i18next.t("general:Action"),
dataIndex: '',
key: 'op',
width: '170px',
width: '240px',
fixed: (Setting.isMobile()) ? "false" : "right",
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/payments/${record.name}/result`)}>{i18next.t("payment:Result")}</Button>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/payments/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm
title={`Sure to delete payment: ${record.name} ?`}

View File

@ -0,0 +1,115 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from "react";
import {Button, Result, Spin} from 'antd';
import * as PaymentBackend from "./backend/PaymentBackend";
import * as Setting from "./Setting";
import i18next from "i18next";
class PaymentResultPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
paymentName: props.match.params.paymentName,
payment: null,
};
}
UNSAFE_componentWillMount() {
this.getPayment();
}
getPayment() {
PaymentBackend.getPayment("admin", this.state.paymentName)
.then((payment) => {
this.setState({
payment: payment,
});
if (payment.state === "Created") {
setTimeout(() => this.getPayment(), 1000);
}
});
}
render() {
const payment = this.state.payment;
if (payment === null) {
return null;
}
if (payment.state === "Paid") {
return (
<div>
{
Setting.renderHelmet(payment)
}
<Result
status="success"
title={`${i18next.t("payment:You have successfully completed the payment")}: ${payment.productDisplayName}`}
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
extra={[
<Button type="primary" key="returnUrl" onClick={() => {
Setting.goToLink(payment.returnUrl);
}}>
{i18next.t("payment:Return to Website")}
</Button>
]}
/>
</div>
)
} else if (payment.state === "Created") {
return (
<div>
{
Setting.renderHelmet(payment)
}
<Result
status="info"
title={`${i18next.t("payment:The payment is still under processing")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}, ${i18next.t("payment:please wait for a few seconds...")}`}
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
extra={[
<Spin size="large" tip={i18next.t("payment:Processing...")} />,
]}
/>
</div>
)
} else {
return (
<div>
{
Setting.renderHelmet(payment)
}
<Result
status="error"
title={`${i18next.t("payment:The payment has failed")}: ${payment.productDisplayName}, ${i18next.t("payment:the current state is")}: ${payment.state}`}
subTitle={i18next.t("payment:Please click the below button to return to the original website")}
extra={[
<Button type="primary" key="returnUrl" onClick={() => {
Setting.goToLink(payment.returnUrl);
}}>
{i18next.t("payment:Return to Website")}
</Button>
]}
/>
</div>
)
}
}
}
export default PaymentResultPage;

View File

@ -186,6 +186,10 @@ class ProductBuyPage extends React.Component {
render() {
const product = this.getProductObj();
if (product === null) {
return null;
}
return (
<div>
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >

View File

@ -212,6 +212,16 @@ class ProductEditPage extends React.Component {
</Select>
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("product:Return URL"), i18next.t("product:Return URL - Tooltip"))} :
</Col>
<Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.product.returnUrl} onChange={e => {
this.updateProductField('returnUrl', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:State"), i18next.t("general:State - Tooltip"))} :

View File

@ -72,6 +72,8 @@ class ProviderEditPage extends React.Component {
case "SMS":
if (this.state.provider.type === "Volc Engine SMS")
return Setting.getLabel(i18next.t("provider:Access key"), i18next.t("provider:Access key - Tooltip"));
if (this.state.provider.type === "Huawei Cloud SMS")
return Setting.getLabel(i18next.t("provider:App key"), i18next.t("provider:App key - Tooltip"));
default:
return Setting.getLabel(i18next.t("provider:Client ID"), i18next.t("provider:Client ID - Tooltip"));
}
@ -84,6 +86,8 @@ class ProviderEditPage extends React.Component {
case "SMS":
if (this.state.provider.type === "Volc Engine SMS")
return Setting.getLabel(i18next.t("provider:Secret access key"), i18next.t("provider:SecretAccessKey - Tooltip"));
if (this.state.provider.type === "Huawei Cloud SMS")
return Setting.getLabel(i18next.t("provider:App secret"), i18next.t("provider:AppSecret - Tooltip"));
default:
return Setting.getLabel(i18next.t("provider:Client secret"), i18next.t("provider:Client secret - Tooltip"));
}
@ -103,6 +107,9 @@ class ProviderEditPage extends React.Component {
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Volc Engine SMS") {
text = i18next.t("provider:SMS account");
tooltip = i18next.t("provider:SMS account - Tooltip");
} else if (this.state.provider.category === "SMS" && this.state.provider.type === "Huawei Cloud SMS") {
text = i18next.t("provider:Channel No.");
tooltip = i18next.t("provider:Channel No. - Tooltip");
} else {
return null;
}
@ -296,7 +303,7 @@ class ProviderEditPage extends React.Component {
)
}
{
this.state.provider.type !== "Adfs" ? null : (
this.state.provider.type !== "Adfs" && this.state.provider.type !== "Casdoor" ? null : (
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :

View File

@ -70,15 +70,7 @@ export function isProviderVisible(providerItem) {
return false;
}
if (providerItem.provider.type === "GitHub") {
if (isLocalhost()) {
return providerItem.provider.name.includes("localhost");
} else {
return !providerItem.provider.name.includes("localhost");
}
} else {
return true;
}
return true;
}
export function isProviderVisibleForSignUp(providerItem) {
@ -404,6 +396,7 @@ export function getProviderTypeOptions(category) {
{id: 'GitLab', name: 'GitLab'},
{id: 'Adfs', name: 'Adfs'},
{id: 'Baidu', name: 'Baidu'},
{id: 'Casdoor', name: 'Casdoor'},
{id: 'Infoflow', name: 'Infoflow'},
{id: 'Apple', name: 'Apple'},
{id: 'AzureAD', name: 'AzureAD'},
@ -423,6 +416,7 @@ export function getProviderTypeOptions(category) {
{id: 'Aliyun SMS', name: 'Aliyun SMS'},
{id: 'Tencent Cloud SMS', name: 'Tencent Cloud SMS'},
{id: 'Volc Engine SMS', name: 'Volc Engine SMS'},
{id: 'Huawei Cloud SMS', name: 'Huawei Cloud SMS'},
]
);
} else if (category === "Storage") {
@ -444,6 +438,7 @@ export function getProviderTypeOptions(category) {
{id: 'Alipay', name: 'Alipay'},
{id: 'WeChat Pay', name: 'WeChat Pay'},
{id: 'PayPal', name: 'PayPal'},
{id: 'GC', name: 'GC'},
]);
} else {
return [];

View File

@ -301,9 +301,24 @@ class UserEditPage extends React.Component {
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.user.tag} onChange={e => {
this.updateUserField('tag', e.target.value);
}} />
{
this.state.application?.organizationObj.tags?.length > 0 ? (
<Select virtual={false} style={{width: '100%'}} value={this.state.user.tag} onChange={(value => {this.updateUserField('tag', value);})}>
{
this.state.application.organizationObj.tags?.map((tag, index) => {
const tokens = tag.split("|");
const value = tokens[0];
const displayValue = Setting.getLanguage() !== "zh" ? tokens[0] : tokens[1];
return <Option key={index} value={value}>{displayValue}</Option>
})
}
</Select>
) : (
<Input value={this.state.user.tag} onChange={e => {
this.updateUserField('tag', e.target.value);
}} />
)
}
</Col>
</Row>
<Row style={{marginTop: '20px'}} >

View File

@ -106,6 +106,7 @@ class AuthCallback extends React.Component {
method: method,
};
const oAuthParams = Util.getOAuthGetParameters(innerParams);
const concatChar = oAuthParams?.redirectUri?.includes('?') ? '&' : '?';
AuthBackend.login(body, oAuthParams)
.then((res) => {
if (res.status === 'ok') {
@ -118,11 +119,11 @@ class AuthCallback extends React.Component {
Setting.goToLink(link);
} else if (responseType === "code") {
const code = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
// Util.showMessage("success", `Authorization code: ${res.data}`);
} else if (responseType === "token" || responseType === "id_token"){
const token = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}?${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}${responseType}=${token}&state=${oAuthParams.state}&token_type=bearer`);
} else if (responseType === "link") {
const from = innerParams.get("from");
Setting.goToLinkSoft(this, from);

View File

@ -0,0 +1,32 @@
// Copyright 2022 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {createButton} from "react-social-login-buttons";
import {StaticBaseUrl} from "../Setting";
function Icon({ width = 24, height = 24, color }) {
return <img src={`${StaticBaseUrl}/buttons/casdoor.svg`} alt="Sign in with Casdoor" style={{width: 24, height: 24}} />;
}
const config = {
text: "Sign in with Casdoor",
icon: Icon,
iconFormat: name => `fa fa-${name}`,
style: {background: "#ffffff", color: "#000000"},
activeStyle: {background: "#ededee"},
};
const CasdoorLoginButton = createButton(config);
export default CasdoorLoginButton;

View File

@ -13,7 +13,7 @@
// limitations under the License.
import React from "react";
import {Button, Col, Form, Select, Input, Row, Steps} from "antd";
import {Button, Col, Form, Input, Row, Select, Steps} from "antd";
import * as AuthBackend from "./AuthBackend";
import * as ApplicationBackend from "../backend/ApplicationBackend";
import * as Util from "./Util";
@ -43,6 +43,7 @@ class ForgetPage extends React.Component {
msg: null,
userId: "",
username: "",
name: "",
email: "",
isFixed: false,
fixedContent: "",
@ -100,7 +101,7 @@ class ForgetPage extends React.Component {
if (res.status === "ok") {
const phone = res.data.phone;
const email = res.data.email;
this.setState({phone: phone, email: email, username: res.data.name});
this.setState({phone: phone, email: email, username: res.data.name, name: res.data.name});
if (phone !== "" && email === "") {
this.setState({
@ -134,15 +135,16 @@ class ForgetPage extends React.Component {
break;
case "step2":
const oAuthParams = Util.getOAuthGetParameters();
if(this.state.verifyType=="email"){
if (this.state.verifyType === "email") {
this.setState({username: this.state.email})
}else if(this.state.verifyType=="phone"){
} else if (this.state.verifyType === "phone") {
this.setState({username: this.state.phone})
}
AuthBackend.login({
application: forms.step2.getFieldValue("application"),
organization: forms.step2.getFieldValue("organization"),
username: this.state.username,
name: this.state.name,
code: forms.step2.getFieldValue("emailCode"),
phonePrefix: this.state.application?.organizationObj.phonePrefix,
type: "login"
@ -179,7 +181,7 @@ class ForgetPage extends React.Component {
if (this.state.phone !== "") {
options.push(
<Option key={"phone"} value={"phone"}>
&nbsp;&nbsp;{Setting.getMaskedPhone(this.state.phone)}
&nbsp;&nbsp;{this.state.phone}
</Option>
);
}
@ -187,7 +189,7 @@ class ForgetPage extends React.Component {
if (this.state.email !== "") {
options.push(
<Option key={"email"} value={"email"}>
&nbsp;&nbsp;{Setting.getMaskedEmail(this.state.email)}
&nbsp;&nbsp;{this.state.email}
</Option>
);
}
@ -349,12 +351,12 @@ class ForgetPage extends React.Component {
{this.state.verifyType === "email" ? (
<CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationOrgName(this.state.application)]}
onButtonClickArgs={[this.state.email, "email", Setting.getApplicationOrgName(this.state.application), this.state.name]}
/>
) : (
<CountDownInput
disabled={this.state.username === "" || this.state.verifyType === ""}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationOrgName(this.state.application)]}
onButtonClickArgs={[this.state.phone, "phone", Setting.getApplicationOrgName(this.state.application), this.state.name]}
/>
)}
</Form.Item>

View File

@ -36,6 +36,7 @@ import LarkLoginButton from "./LarkLoginButton";
import GitLabLoginButton from "./GitLabLoginButton";
import AdfsLoginButton from "./AdfsLoginButton";
import BaiduLoginButton from "./BaiduLoginButton";
import CasdoorLoginButton from "./CasdoorLoginButton";
import InfoflowLoginButton from "./InfoflowLoginButton";
import AppleLoginButton from "./AppleLoginButton"
import AzureADLoginButton from "./AzureADLoginButton";
@ -56,7 +57,9 @@ class LoginPage extends React.Component {
isCodeSignin: false,
msg: null,
username: null,
validEmailOrPhone: false
validEmailOrPhone: false,
validEmail: false,
validPhone: false,
};
}
@ -135,6 +138,7 @@ class LoginPage extends React.Component {
Setting.goToLink(link);
} else if (responseType === "code") {
const code = res.data;
const concatChar = oAuthParams?.redirectUri?.includes('?') ? '&' : '?';
if (Setting.hasPromptPage(application)) {
AuthBackend.getAccount("")
@ -147,7 +151,7 @@ class LoginPage extends React.Component {
this.onUpdateAccount(account);
if (Setting.isPromptAnswered(account, application)) {
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
} else {
Setting.goToLinkSoft(ths, `/prompt/${application.name}?redirectUri=${oAuthParams.redirectUri}&code=${code}&state=${oAuthParams.state}`);
}
@ -156,7 +160,7 @@ class LoginPage extends React.Component {
}
});
} else {
Setting.goToLink(`${oAuthParams.redirectUri}?code=${code}&state=${oAuthParams.state}`);
Setting.goToLink(`${oAuthParams.redirectUri}${concatChar}code=${code}&state=${oAuthParams.state}`);
}
// Util.showMessage("success", `Authorization code: ${res.data}`);
@ -198,6 +202,8 @@ class LoginPage extends React.Component {
return <GitLabLoginButton text={text} align={"center"} />
} else if (type === "Adfs") {
return <AdfsLoginButton text={text} align={"center"} />
} else if (type === "Casdoor") {
return <CasdoorLoginButton text={text} align={"center"} />
} else if (type === "Baidu") {
return <BaiduLoginButton text={text} align={"center"} />
} else if (type === "Infoflow") {
@ -344,6 +350,12 @@ class LoginPage extends React.Component {
return Promise.reject(i18next.t("login:The input is not valid Email or Phone!"));
}
}
if (Setting.isValidPhone(this.state.username)) {
this.setState({validPhone: true})
}
if (Setting.isValidEmail(this.state.username)) {
this.setState({validEmail: true})
}
this.setState({validEmailOrPhone: true});
return Promise.resolve();
}
@ -369,7 +381,7 @@ class LoginPage extends React.Component {
>
<CountDownInput
disabled={this.state.username?.length === 0 || !this.state.validEmailOrPhone}
onButtonClickArgs={[this.state.username, "", Setting.getApplicationOrgName(application), true]}
onButtonClickArgs={[this.state.username, this.state.validEmail ? "email" : "phone", Setting.getApplicationOrgName(application)]}
/>
</Form.Item>
) : (

View File

@ -147,7 +147,10 @@ class PromptPage extends React.Component {
if (res.status === 'ok') {
this.onUpdateAccount(null);
const redirectUrl = this.getRedirectUrl();
let redirectUrl = this.getRedirectUrl();
if (redirectUrl === "") {
redirectUrl = res.data2
}
if (redirectUrl !== "") {
Setting.goToLink(redirectUrl);
} else {

View File

@ -15,7 +15,7 @@
import React from "react";
import {Tooltip} from "antd";
import * as Util from "./Util";
import {StaticBaseUrl} from "../Setting";
import * as Setting from "../Setting";
const authInfo = {
Google: {
@ -78,6 +78,10 @@ const authInfo = {
scope: "basic",
endpoint: "http://openapi.baidu.com/oauth/2.0/authorize",
},
Casdoor: {
scope: "openid%20profile%20email",
endpoint: "http://example.com",
},
Infoflow: {
endpoint: "https://xpc.im.baidu.com/oauth2/authorize",
},
@ -101,71 +105,79 @@ const authInfo = {
const otherProviderInfo = {
SMS: {
"Aliyun SMS": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
logo: `${Setting.StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/sms",
},
"Tencent Cloud SMS": {
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
logo: `${Setting.StaticBaseUrl}/img/social_tencent_cloud.jpg`,
url: "https://cloud.tencent.com/product/sms",
},
"Volc Engine SMS": {
logo: `${StaticBaseUrl}/img/social_volc_engine.jpg`,
logo: `${Setting.StaticBaseUrl}/img/social_volc_engine.jpg`,
url: "https://www.volcengine.com/products/cloud-sms",
},
"Huawei Cloud SMS": {
logo: `${Setting.StaticBaseUrl}/img/social_huawei.png`,
url: "https://www.huaweicloud.com/product/msgsms.html",
},
},
Email: {
"Default": {
logo: `${StaticBaseUrl}/img/social_default.png`,
logo: `${Setting.StaticBaseUrl}/img/social_default.png`,
url: "",
},
},
Storage: {
"Local File System": {
logo: `${StaticBaseUrl}/img/social_file.png`,
logo: `${Setting.StaticBaseUrl}/img/social_file.png`,
url: "",
},
"AWS S3": {
logo: `${StaticBaseUrl}/img/social_aws.png`,
logo: `${Setting.StaticBaseUrl}/img/social_aws.png`,
url: "https://aws.amazon.com/s3",
},
"Aliyun OSS": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
logo: `${Setting.StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/oss",
},
"Tencent Cloud COS": {
logo: `${StaticBaseUrl}/img/social_tencent_cloud.jpg`,
logo: `${Setting.StaticBaseUrl}/img/social_tencent_cloud.jpg`,
url: "https://cloud.tencent.com/product/cos",
},
},
SAML: {
"Aliyun IDaaS": {
logo: `${StaticBaseUrl}/img/social_aliyun.png`,
logo: `${Setting.StaticBaseUrl}/img/social_aliyun.png`,
url: "https://aliyun.com/product/idaas"
},
"Keycloak": {
logo: `${StaticBaseUrl}/img/social_keycloak.png`,
logo: `${Setting.StaticBaseUrl}/img/social_keycloak.png`,
url: "https://www.keycloak.org/"
},
},
Payment: {
"Alipay": {
logo: `${StaticBaseUrl}/img/payment_alipay.png`,
logo: `${Setting.StaticBaseUrl}/img/payment_alipay.png`,
url: "https://www.alipay.com/"
},
"WeChat Pay": {
logo: `${StaticBaseUrl}/img/payment_wechat_pay.png`,
logo: `${Setting.StaticBaseUrl}/img/payment_wechat_pay.png`,
url: "https://pay.weixin.qq.com/"
},
"PayPal": {
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
logo: `${Setting.StaticBaseUrl}/img/payment_paypal.png`,
url: "https://www.paypal.com/"
},
"GC": {
logo: `${Setting.StaticBaseUrl}/img/payment_gc.png`,
url: "https://gc.org"
},
},
};
export function getProviderLogo(provider) {
if (provider.category === "OAuth") {
return `${StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
return `${Setting.StaticBaseUrl}/img/social_${provider.type.toLowerCase()}.png`;
} else {
return otherProviderInfo[provider.category][provider.type].logo;
}
@ -275,6 +287,8 @@ export function getAuthUrl(application, provider, method) {
return `${provider.domain}/adfs/oauth2/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&nonce=casdoor&scope=openid`;
} else if (provider.type === "Baidu") {
return `${endpoint}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}&display=popup`;
} else if (provider.type === "Casdoor") {
return `${provider.domain}/login/oauth/authorize?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${scope}`;
} else if (provider.type === "Infoflow"){
return `${endpoint}?appid=${provider.clientId}&redirect_uri=${redirectUri}?state=${state}`
} else if (provider.type === "Apple") {

View File

@ -56,7 +56,7 @@ export function deleteProduct(product) {
}
export function buyProduct(owner, name, providerId) {
return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerId=${providerId}`, {
return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerName=${providerId}`, {
method: 'POST',
credentials: 'include',
}).then(res => res.json());

View File

@ -49,14 +49,6 @@ export const CountDownInput = (props) => {
const handleOk = () => {
setVisible(false);
if (isValidEmail(onButtonClickArgs[0])) {
onButtonClickArgs[1] = "email";
} else if (isValidPhone(onButtonClickArgs[0])) {
onButtonClickArgs[1] = "phone";
} else {
Util.showMessage("error", i18next.t("login:Invalid Email or phone"))
return;
}
setButtonLoading(true)
UserBackend.sendCode(checkType, checkId, key, ...onButtonClickArgs).then(res => {
setKey("");

View File

@ -113,6 +113,7 @@
"Edit": "Bearbeiten",
"Email": "E-Mail",
"Email - Tooltip": "email",
"Favicon": "Favicon",
"Favicon - Tooltip": "Application icon",
"First name": "First name",
"Forget URL": "URL vergessen",
@ -248,6 +249,8 @@
"New Organization": "New Organization",
"Soft deletion": "Weiche Löschung",
"Soft deletion - Tooltip": "Weiche Löschung - Tooltip",
"Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip",
"Website URL": "Website-URL",
"Website URL - Tooltip": "Unique string-style identifier"
},
@ -256,12 +259,23 @@
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"New Payment": "New Payment",
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Processing...": "Processing...",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Result": "Result",
"Return to Website": "Return to Website",
"State": "State",
"State - Tooltip": "State - Tooltip",
"The payment has failed": "The payment has failed",
"The payment is still under processing": "The payment is still under processing",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
"Type - Tooltip": "Type - Tooltip",
"You have successfully completed the payment": "You have successfully completed the payment",
"please wait for a few seconds...": "please wait for a few seconds...",
"the current state is": "the current state is"
},
"permission": {
"Actions": "Aktionen",
@ -296,6 +310,8 @@
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",
"Quantity - Tooltip": "Quantity - Tooltip",
"Return URL": "Return URL",
"Return URL - Tooltip": "Return URL - Tooltip",
"SKU": "SKU",
"Sold": "Sold",
"Sold - Tooltip": "Sold - Tooltip",

View File

@ -113,6 +113,7 @@
"Edit": "Edit",
"Email": "Email",
"Email - Tooltip": "Email - Tooltip",
"Favicon": "Favicon",
"Favicon - Tooltip": "Favicon - Tooltip",
"First name": "First name",
"Forget URL": "Forget URL",
@ -248,6 +249,8 @@
"New Organization": "New Organization",
"Soft deletion": "Soft deletion",
"Soft deletion - Tooltip": "Soft deletion - Tooltip",
"Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip",
"Website URL": "Website URL",
"Website URL - Tooltip": "Website URL - Tooltip"
},
@ -256,12 +259,23 @@
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"New Payment": "New Payment",
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Processing...": "Processing...",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Result": "Result",
"Return to Website": "Return to Website",
"State": "State",
"State - Tooltip": "State - Tooltip",
"The payment has failed": "The payment has failed",
"The payment is still under processing": "The payment is still under processing",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
"Type - Tooltip": "Type - Tooltip",
"You have successfully completed the payment": "You have successfully completed the payment",
"please wait for a few seconds...": "please wait for a few seconds...",
"the current state is": "the current state is"
},
"permission": {
"Actions": "Actions",
@ -296,6 +310,8 @@
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",
"Quantity - Tooltip": "Quantity - Tooltip",
"Return URL": "Return URL",
"Return URL - Tooltip": "Return URL - Tooltip",
"SKU": "SKU",
"Sold": "Sold",
"Sold - Tooltip": "Sold - Tooltip",

View File

@ -113,6 +113,7 @@
"Edit": "Editer",
"Email": "Courriel",
"Email - Tooltip": "email",
"Favicon": "Favicon",
"Favicon - Tooltip": "Application icon",
"First name": "First name",
"Forget URL": "Oublier l'URL",
@ -248,6 +249,8 @@
"New Organization": "New Organization",
"Soft deletion": "Suppression du logiciel",
"Soft deletion - Tooltip": "Suppression de soft - infobulle",
"Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip",
"Website URL": "URL du site web",
"Website URL - Tooltip": "Unique string-style identifier"
},
@ -256,12 +259,23 @@
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"New Payment": "New Payment",
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Processing...": "Processing...",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Result": "Result",
"Return to Website": "Return to Website",
"State": "State",
"State - Tooltip": "State - Tooltip",
"The payment has failed": "The payment has failed",
"The payment is still under processing": "The payment is still under processing",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
"Type - Tooltip": "Type - Tooltip",
"You have successfully completed the payment": "You have successfully completed the payment",
"please wait for a few seconds...": "please wait for a few seconds...",
"the current state is": "the current state is"
},
"permission": {
"Actions": "Actions",
@ -296,6 +310,8 @@
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",
"Quantity - Tooltip": "Quantity - Tooltip",
"Return URL": "Return URL",
"Return URL - Tooltip": "Return URL - Tooltip",
"SKU": "SKU",
"Sold": "Sold",
"Sold - Tooltip": "Sold - Tooltip",

View File

@ -113,6 +113,7 @@
"Edit": "編集",
"Email": "Eメールアドレス",
"Email - Tooltip": "email",
"Favicon": "Favicon",
"Favicon - Tooltip": "Application icon",
"First name": "First name",
"Forget URL": "URLを忘れた",
@ -248,6 +249,8 @@
"New Organization": "New Organization",
"Soft deletion": "ソフト削除",
"Soft deletion - Tooltip": "ソフト削除 - ツールチップ",
"Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip",
"Website URL": "Website URL",
"Website URL - Tooltip": "Unique string-style identifier"
},
@ -256,12 +259,23 @@
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"New Payment": "New Payment",
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Processing...": "Processing...",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Result": "Result",
"Return to Website": "Return to Website",
"State": "State",
"State - Tooltip": "State - Tooltip",
"The payment has failed": "The payment has failed",
"The payment is still under processing": "The payment is still under processing",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
"Type - Tooltip": "Type - Tooltip",
"You have successfully completed the payment": "You have successfully completed the payment",
"please wait for a few seconds...": "please wait for a few seconds...",
"the current state is": "the current state is"
},
"permission": {
"Actions": "アクション",
@ -296,6 +310,8 @@
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",
"Quantity - Tooltip": "Quantity - Tooltip",
"Return URL": "Return URL",
"Return URL - Tooltip": "Return URL - Tooltip",
"SKU": "SKU",
"Sold": "Sold",
"Sold - Tooltip": "Sold - Tooltip",

View File

@ -113,6 +113,7 @@
"Edit": "Edit",
"Email": "Email",
"Email - Tooltip": "email",
"Favicon": "Favicon",
"Favicon - Tooltip": "Application icon",
"First name": "First name",
"Forget URL": "Forget URL",
@ -248,6 +249,8 @@
"New Organization": "New Organization",
"Soft deletion": "Soft deletion",
"Soft deletion - Tooltip": "Soft deletion - Tooltip",
"Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip",
"Website URL": "Website URL",
"Website URL - Tooltip": "Unique string-style identifier"
},
@ -256,12 +259,23 @@
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"New Payment": "New Payment",
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Processing...": "Processing...",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Result": "Result",
"Return to Website": "Return to Website",
"State": "State",
"State - Tooltip": "State - Tooltip",
"The payment has failed": "The payment has failed",
"The payment is still under processing": "The payment is still under processing",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
"Type - Tooltip": "Type - Tooltip",
"You have successfully completed the payment": "You have successfully completed the payment",
"please wait for a few seconds...": "please wait for a few seconds...",
"the current state is": "the current state is"
},
"permission": {
"Actions": "Actions",
@ -296,6 +310,8 @@
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",
"Quantity - Tooltip": "Quantity - Tooltip",
"Return URL": "Return URL",
"Return URL - Tooltip": "Return URL - Tooltip",
"SKU": "SKU",
"Sold": "Sold",
"Sold - Tooltip": "Sold - Tooltip",

View File

@ -113,6 +113,7 @@
"Edit": "Редактирование",
"Email": "Почта",
"Email - Tooltip": "email",
"Favicon": "Favicon",
"Favicon - Tooltip": "Application icon",
"First name": "First name",
"Forget URL": "Забыть URL",
@ -248,6 +249,8 @@
"New Organization": "New Organization",
"Soft deletion": "Мягкое удаление",
"Soft deletion - Tooltip": "Мягкое удаление - Подсказка",
"Tags": "Tags",
"Tags - Tooltip": "Tags - Tooltip",
"Website URL": "URL сайта",
"Website URL - Tooltip": "Unique string-style identifier"
},
@ -256,12 +259,23 @@
"Currency - Tooltip": "Currency - Tooltip",
"Edit Payment": "Edit Payment",
"New Payment": "New Payment",
"Please click the below button to return to the original website": "Please click the below button to return to the original website",
"Price": "Price",
"Price - Tooltip": "Price - Tooltip",
"Processing...": "Processing...",
"Product": "Product",
"Product - Tooltip": "Product - Tooltip",
"Result": "Result",
"Return to Website": "Return to Website",
"State": "State",
"State - Tooltip": "State - Tooltip",
"The payment has failed": "The payment has failed",
"The payment is still under processing": "The payment is still under processing",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip"
"Type - Tooltip": "Type - Tooltip",
"You have successfully completed the payment": "You have successfully completed the payment",
"please wait for a few seconds...": "please wait for a few seconds...",
"the current state is": "the current state is"
},
"permission": {
"Actions": "Действия",
@ -296,6 +310,8 @@
"Price - Tooltip": "Price - Tooltip",
"Quantity": "Quantity",
"Quantity - Tooltip": "Quantity - Tooltip",
"Return URL": "Return URL",
"Return URL - Tooltip": "Return URL - Tooltip",
"SKU": "SKU",
"Sold": "Sold",
"Sold - Tooltip": "Sold - Tooltip",

View File

@ -20,8 +20,8 @@
"Password ON": "开启密码",
"Password ON - Tooltip": "是否允许密码登录",
"Please select a HTML file": "请选择一个HTML文件",
"Redirect URL": "回调URL",
"Redirect URLs": "回调URLs",
"Redirect URL": "重定向 URL",
"Redirect URLs": "重定向 URLs",
"Redirect URLs - Tooltip": "登录成功后重定向地址列表",
"Refresh token expire": "Refresh Token过期时间",
"Refresh token expire - Tooltip": "Refresh Token过期时间",
@ -113,7 +113,8 @@
"Edit": "编辑",
"Email": "电子邮箱",
"Email - Tooltip": "电子邮件:",
"Favicon - Tooltip": "网站图标",
"Favicon": "网站图标",
"Favicon - Tooltip": "网站的Favicon图标",
"First name": "名字",
"Forget URL": "忘记密码URL",
"Forget URL - Tooltip": "忘记密码URL",
@ -248,6 +249,8 @@
"New Organization": "添加组织",
"Soft deletion": "软删除",
"Soft deletion - Tooltip": "启用后,删除用户信息时不会在数据库彻底清除,只会标记为已删除状态",
"Tags": "标签集合",
"Tags - Tooltip": "可供用户选择的标签的集合",
"Website URL": "网页地址",
"Website URL - Tooltip": "网页地址"
},
@ -256,12 +259,23 @@
"Currency - Tooltip": "如USD美元CNY人民币等",
"Edit Payment": "编辑付款",
"New Payment": "添加付款",
"Please click the below button to return to the original website": "请点击下方按钮返回原网站",
"Price": "价格",
"Price - Tooltip": "商品价格",
"Processing...": "正在处理...",
"Product": "商品",
"Product - Tooltip": "商品名称",
"Result": "结果",
"Return to Website": "返回原网站",
"State": "状态",
"State - Tooltip": "交易状态",
"The payment has failed": "支付失败",
"The payment is still under processing": "支付正在处理",
"Type": "支付方式",
"Type - Tooltip": "商品购买时的支付方式"
"Type - Tooltip": "商品购买时的支付方式",
"You have successfully completed the payment": "支付成功",
"please wait for a few seconds...": "请稍后...",
"the current state is": "当前状态为"
},
"permission": {
"Actions": "动作",
@ -290,12 +304,14 @@
"Pay": "支付方式",
"Payment providers": "支付提供商",
"Payment providers - Tooltip": "支付提供商 - 工具提示",
"Paypal": "Paypal",
"Paypal": "PayPal(贝宝)",
"Placing order...": "正在下单...",
"Price": "价格",
"Price - Tooltip": "价格 - 工具提示",
"Quantity": "库存",
"Quantity - Tooltip": "库存 - 工具提示",
"Return URL": "返回URL",
"Return URL - Tooltip": "返回URL - 工具提示",
"SKU": "货号",
"Sold": "售出",
"Sold - Tooltip": "售出 - 工具提示",

BIN
xlsx/user_test.xlsx Normal file

Binary file not shown.

View File

@ -13,6 +13,7 @@
// limitations under the License.
//go:build !skipCi
// +build !skipCi
package xlsx