mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-02 10:43:36 +08:00
Compare commits
36 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
16f427d4d7 | ||
![]() |
6ad8f9fa0b | ||
![]() |
b93a29fbc6 | ||
![]() |
09f430266b | ||
![]() |
52d9017611 | ||
![]() |
c70c62f52e | ||
![]() |
355b0b35d0 | ||
![]() |
e4846807cd | ||
![]() |
f4a59de3a5 | ||
![]() |
a1b16f88d1 | ||
![]() |
90ec8ec787 | ||
![]() |
ea8971ff29 | ||
![]() |
bd41425039 | ||
![]() |
9d9a1da07f | ||
![]() |
465d25a272 | ||
![]() |
ef1195960e | ||
![]() |
089f4ff480 | ||
![]() |
88aa444ad1 | ||
![]() |
1c5ce46bd5 | ||
![]() |
14d09cad2c | ||
![]() |
06006c87b8 | ||
![]() |
a4edf47dc4 | ||
![]() |
e68b0198f1 | ||
![]() |
015961bc3c | ||
![]() |
5d98cc6ac5 | ||
![]() |
b3eec024b8 | ||
![]() |
eefcfd8440 | ||
![]() |
c6b2106c94 | ||
![]() |
edf621f4d5 | ||
![]() |
e50c6cd4b5 | ||
![]() |
9c3117beb0 | ||
![]() |
4ca307564c | ||
![]() |
15a6f64fdc | ||
![]() |
75e917a070 | ||
![]() |
e1182bb635 | ||
![]() |
2b70698c2a |
1
.github/workflows/build.yml
vendored
1
.github/workflows/build.yml
vendored
@@ -31,6 +31,7 @@ jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
|
||||
needs: [ frontend, backend ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
@@ -1,7 +1,7 @@
|
||||
FROM golang:1.16 AS BACK
|
||||
WORKDIR /go/src/casdoor
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o server . \
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPROXY=https://goproxy.cn,direct go build -ldflags="-w -s" -o server . \
|
||||
&& apt update && apt install wait-for-it && chmod +x /usr/bin/wait-for-it
|
||||
|
||||
FROM node:14.17.4 AS FRONT
|
||||
|
@@ -87,6 +87,7 @@ Edit `conf/app.conf`, set `runmode=dev`. Firstly build front-end files:
|
||||
```bash
|
||||
cd web/ && npm install && npm run start
|
||||
```
|
||||
*❗ A word of caution ❗: the `npm` commands above need a recommended system RAM of at least 4GB. It has a potential failure during building the files if your RAM is not sufficient.*
|
||||
|
||||
Then build back-end binary file, change directory to root(Relative to casdoor):
|
||||
|
||||
@@ -94,7 +95,8 @@ Then build back-end binary file, change directory to root(Relative to casdoor):
|
||||
go run main.go
|
||||
```
|
||||
|
||||
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
|
||||
That's it! Try to visit http://127.0.0.1:7001/. :small_airplane:
|
||||
**But make sure you always request the backend port 8000 when you are using SDKs.**
|
||||
|
||||
##### Production Mode
|
||||
|
||||
|
@@ -71,6 +71,7 @@ m = (r.subOwner == p.subOwner || p.subOwner == "*") && \
|
||||
if true {
|
||||
ruleText := `
|
||||
p, built-in, *, *, *, *, *
|
||||
p, app, *, *, *, *, *
|
||||
p, *, *, POST, /api/signup, *, *
|
||||
p, *, *, POST, /api/get-email-and-phone, *, *
|
||||
p, *, *, POST, /api/login, *, *
|
||||
@@ -84,12 +85,17 @@ p, *, *, GET, /api/get-user, *, *
|
||||
p, *, *, GET, /api/get-organizations, *, *
|
||||
p, *, *, GET, /api/get-user-application, *, *
|
||||
p, *, *, GET, /api/get-default-providers, *, *
|
||||
p, *, *, GET, /api/get-resources, *, *
|
||||
p, *, *, POST, /api/upload-avatar, *, *
|
||||
p, *, *, POST, /api/unlink, *, *
|
||||
p, *, *, POST, /api/set-password, *, *
|
||||
p, *, *, POST, /api/send-verification-code, *, *
|
||||
p, *, *, GET, /api/get-human-check, *, *
|
||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||
p, *, *, POST, /api/upload-resource, *, *
|
||||
p, *, *, POST, /api/paypal, *, *
|
||||
p, *, *, GET, /api/success-pay, *, *
|
||||
p, *, *, GET, /api/get-application-clientId, *, *
|
||||
`
|
||||
|
||||
sa := stringadapter.NewAdapter(ruleText)
|
||||
|
@@ -7,6 +7,7 @@ driverName = mysql
|
||||
dataSourceName = root:123@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
authState = "casdoor"
|
||||
httpProxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
|
@@ -148,6 +148,7 @@ func (c *ApiController) Signup() {
|
||||
Address: []string{},
|
||||
Affiliation: form.Affiliation,
|
||||
Region: form.Region,
|
||||
Score: getInitScore(),
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
IsForbidden: false,
|
||||
|
@@ -16,6 +16,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casbin/casdoor/object"
|
||||
)
|
||||
|
||||
@@ -116,3 +117,10 @@ func (c *ApiController) DeleteApplication() {
|
||||
c.Data["json"] = wrapActionResponse(object.DeleteApplication(&application))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetApplicationByClientId() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
|
||||
c.Data["json"] = object.GetApplicationByClientId(clientId)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/idp"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/proxy"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
@@ -99,9 +100,9 @@ func (c *ApiController) GetApplicationLogin() {
|
||||
|
||||
func setHttpClient(idProvider idp.IdProvider, providerType string) {
|
||||
if providerType == "GitHub" || providerType == "Google" || providerType == "Facebook" || providerType == "LinkedIn" {
|
||||
idProvider.SetHttpClient(proxyHttpClient)
|
||||
idProvider.SetHttpClient(proxy.ProxyHttpClient)
|
||||
} else {
|
||||
idProvider.SetHttpClient(defaultHttpClient)
|
||||
idProvider.SetHttpClient(proxy.DefaultHttpClient)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,12 +277,6 @@ func (c *ApiController) Login() {
|
||||
return
|
||||
}
|
||||
|
||||
var score int
|
||||
score, err = strconv.Atoi(beego.AppConfig.String("initScore"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
properties := map[string]string{}
|
||||
properties["no"] = strconv.Itoa(len(object.GetUsers(application.Organization)) + 2)
|
||||
user := &object.User{
|
||||
@@ -292,8 +287,9 @@ func (c *ApiController) Login() {
|
||||
Type: "normal-user",
|
||||
DisplayName: userInfo.DisplayName,
|
||||
Avatar: userInfo.AvatarUrl,
|
||||
Address: []string{},
|
||||
Email: userInfo.Email,
|
||||
Score: score,
|
||||
Score: getInitScore(),
|
||||
IsAdmin: false,
|
||||
IsGlobalAdmin: false,
|
||||
IsForbidden: false,
|
||||
|
45
controllers/payment.go
Normal file
45
controllers/payment.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/payment"
|
||||
)
|
||||
|
||||
func (c *ApiController) PaypalPay() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
redirectUri := c.Input().Get("redirectUri")
|
||||
var payItem object.PayItem
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payItem)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
msg := payment.Paypal(payItem, clientId, redirectUri)
|
||||
|
||||
c.Data["json"] = msg
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetPayments() {
|
||||
c.Data["json"] = object.GetPayments()
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) DeletePayment() {
|
||||
var payment object.Payment
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &payment)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
c.Data["json"] = wrapActionResponse(object.DeletePayment(&payment))
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) SuccessPay() {
|
||||
token := c.Input().Get("paymentId")
|
||||
c.Data["json"] = payment.SuccessPay(token)
|
||||
c.ServeJSON()
|
||||
}
|
@@ -19,8 +19,8 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
@@ -28,8 +28,9 @@ import (
|
||||
|
||||
func (c *ApiController) GetResources() {
|
||||
owner := c.Input().Get("owner")
|
||||
user := c.Input().Get("user")
|
||||
|
||||
c.Data["json"] = object.GetResources(owner)
|
||||
c.Data["json"] = object.GetResources(owner, user)
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
@@ -64,32 +65,6 @@ func (c *ApiController) AddResource() {
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
func (c *ApiController) GetProviderParam() (*object.Provider, *object.User, bool) {
|
||||
providerName := c.Input().Get("provider")
|
||||
if providerName != "" {
|
||||
provider := object.GetProvider(util.GetId(providerName))
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf("The provider: %s is not found", providerName))
|
||||
return nil, nil, false
|
||||
}
|
||||
return provider, nil, true
|
||||
}
|
||||
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
user := object.GetUser(userId)
|
||||
application := object.GetApplicationByUser(user)
|
||||
provider := application.GetStorageProvider()
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf("No storage provider is found for application: %s", application.Name))
|
||||
return nil, nil, false
|
||||
}
|
||||
return provider, user, true
|
||||
}
|
||||
|
||||
func (c *ApiController) DeleteResource() {
|
||||
var resource object.Resource
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &resource)
|
||||
@@ -97,12 +72,12 @@ func (c *ApiController) DeleteResource() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
provider, _, ok := c.GetProviderParam()
|
||||
provider, _, ok := c.GetProviderFromContext("Storage")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
err = object.DeleteFile(provider, resource.ObjectKey)
|
||||
err = object.DeleteFile(provider, resource.Name)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
@@ -114,16 +89,18 @@ func (c *ApiController) DeleteResource() {
|
||||
|
||||
func (c *ApiController) UploadResource() {
|
||||
owner := c.Input().Get("owner")
|
||||
username := c.Input().Get("user")
|
||||
application := c.Input().Get("application")
|
||||
tag := c.Input().Get("tag")
|
||||
parent := c.Input().Get("parent")
|
||||
fullFilePath := c.Input().Get("fullFilePath")
|
||||
|
||||
file, header, err := c.GetFile("file")
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
filename := filepath.Base(fullFilePath)
|
||||
fileBuffer := bytes.NewBuffer(nil)
|
||||
@@ -132,17 +109,19 @@ func (c *ApiController) UploadResource() {
|
||||
return
|
||||
}
|
||||
|
||||
provider, user, ok := c.GetProviderParam()
|
||||
provider, user, ok := c.GetProviderFromContext("Storage")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fileType := "unknown"
|
||||
contentType := header.Header.Get("Content-Type")
|
||||
if strings.HasPrefix(contentType, "image/") {
|
||||
fileType = "image"
|
||||
} else if strings.HasPrefix(contentType, "video/") {
|
||||
fileType = "video"
|
||||
fileType, _ = util.GetOwnerAndNameFromId(contentType)
|
||||
|
||||
if fileType != "image" && fileType != "video" {
|
||||
ext := filepath.Ext(filename)
|
||||
mimeType := mime.TypeByExtension(ext)
|
||||
fileType, _ = util.GetOwnerAndNameFromId(mimeType)
|
||||
}
|
||||
|
||||
fileUrl, objectKey, err := object.UploadFile(provider, fullFilePath, fileBuffer)
|
||||
@@ -155,21 +134,28 @@ func (c *ApiController) UploadResource() {
|
||||
fileSize := int(header.Size)
|
||||
resource := &object.Resource{
|
||||
Owner: owner,
|
||||
Name: filename,
|
||||
Name: objectKey,
|
||||
CreatedTime: util.GetCurrentTime(),
|
||||
User: username,
|
||||
Provider: provider.Name,
|
||||
Application: application,
|
||||
Tag: tag,
|
||||
Parent: parent,
|
||||
FileName: filename,
|
||||
FileType: fileType,
|
||||
FileFormat: fileFormat,
|
||||
FileSize: fileSize,
|
||||
Url: fileUrl,
|
||||
ObjectKey: objectKey,
|
||||
}
|
||||
object.AddOrUpdateResource(resource)
|
||||
|
||||
switch tag {
|
||||
case "avatar":
|
||||
if user == nil {
|
||||
c.ResponseError("user is nil for tag: \"avatar\"")
|
||||
return
|
||||
}
|
||||
|
||||
user.Avatar = fileUrl
|
||||
object.UpdateUser(user.GetId(), user)
|
||||
case "termsOfUse":
|
||||
@@ -179,5 +165,5 @@ func (c *ApiController) UploadResource() {
|
||||
object.UpdateApplication(applicationId, app)
|
||||
}
|
||||
|
||||
c.ResponseOk(fileUrl)
|
||||
c.ResponseOk(fileUrl, objectKey)
|
||||
}
|
||||
|
@@ -19,126 +19,87 @@ package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
sender "github.com/casdoor/go-sms-sender"
|
||||
)
|
||||
|
||||
// SendEmail
|
||||
// @Title SendEmail
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param body body emailForm true "Details of the email request"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param body body emailForm true "Details of the email request"
|
||||
// @Success 200 {object} Response object
|
||||
// @router /api/send-email [post]
|
||||
func (c *ApiController) SendEmail() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
clientSecret := c.Input().Get("clientSecret")
|
||||
app := object.GetApplicationByClientIdAndSecret(clientId, clientSecret)
|
||||
if app == nil {
|
||||
c.ResponseError("Invalid clientId or clientSecret.")
|
||||
return
|
||||
}
|
||||
|
||||
provider := app.GetEmailProvider()
|
||||
if provider == nil {
|
||||
c.ResponseError("No Email provider is found")
|
||||
provider, _, ok := c.GetProviderFromContext("Email")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var emailForm struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Receivers []string `json:"receivers"`
|
||||
Sender string `json:"sender"`
|
||||
Receivers []string `json:"receivers"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &emailForm)
|
||||
if err != nil {
|
||||
c.ResponseError("Request body error.")
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if util.IsStrsEmpty(emailForm.Title, emailForm.Content, emailForm.Sender) {
|
||||
c.ResponseError("Missing parameters.")
|
||||
c.ResponseError(fmt.Sprintf("Empty parameters for emailForm: %v", emailForm))
|
||||
return
|
||||
}
|
||||
|
||||
var invalidEmails []string
|
||||
invalidReceivers := []string{}
|
||||
for _, receiver := range emailForm.Receivers {
|
||||
if !util.IsEmailValid(receiver) {
|
||||
invalidEmails = append(invalidEmails, receiver)
|
||||
invalidReceivers = append(invalidReceivers, receiver)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidEmails) != 0 {
|
||||
c.ResponseError("Invalid Email addresses", invalidEmails)
|
||||
if len(invalidReceivers) != 0 {
|
||||
c.ResponseError(fmt.Sprintf("Invalid Email receivers: %s", invalidReceivers))
|
||||
return
|
||||
}
|
||||
|
||||
ok := 0
|
||||
for _, receiver := range emailForm.Receivers {
|
||||
if msg := object.SendEmail(
|
||||
provider,
|
||||
emailForm.Title,
|
||||
emailForm.Content,
|
||||
receiver,
|
||||
emailForm.Sender); len(msg) == 0 {
|
||||
ok++
|
||||
err = object.SendEmail(provider, emailForm.Title, emailForm.Content, receiver, emailForm.Sender)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = Response{Status: "ok", Data: ok}
|
||||
c.ServeJSON()
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
||||
// SendSms
|
||||
// @Title SendSms
|
||||
// @Description This API is not for Casdoor frontend to call, it is for Casdoor SDKs.
|
||||
// @Param clientId query string true "The clientId of the application"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param body body smsForm true "Details of the sms request"
|
||||
// @Param clientSecret query string true "The clientSecret of the application"
|
||||
// @Param body body smsForm true "Details of the sms request"
|
||||
// @Success 200 {object} Response object
|
||||
// @router /api/send-sms [post]
|
||||
func (c *ApiController) SendSms() {
|
||||
clientId := c.Input().Get("clientId")
|
||||
clientSecret := c.Input().Get("clientSecret")
|
||||
app := object.GetApplicationByClientIdAndSecret(clientId, clientSecret)
|
||||
if app == nil {
|
||||
c.ResponseError("Invalid clientId or clientSecret.")
|
||||
return
|
||||
}
|
||||
|
||||
provider := app.GetSmsProvider()
|
||||
if provider == nil {
|
||||
c.ResponseError("No SMS provider is found")
|
||||
return
|
||||
}
|
||||
|
||||
client := sender.NewSmsClient(
|
||||
provider.Type,
|
||||
provider.ClientId,
|
||||
provider.ClientSecret,
|
||||
provider.SignName,
|
||||
provider.RegionId,
|
||||
provider.TemplateCode,
|
||||
provider.AppId,
|
||||
)
|
||||
if client == nil {
|
||||
c.ResponseError("Invalid provider info.")
|
||||
provider, _, ok := c.GetProviderFromContext("SMS")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var smsForm struct {
|
||||
Receivers []string `json:"receivers"`
|
||||
Parameters map[string]string `json:"parameters"`
|
||||
Content string `json:"content"`
|
||||
Receivers []string `json:"receivers"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal(c.Ctx.Input.RequestBody, &smsForm)
|
||||
if err != nil {
|
||||
c.ResponseError("Request body error.")
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -150,11 +111,15 @@ func (c *ApiController) SendSms() {
|
||||
}
|
||||
|
||||
if len(invalidReceivers) != 0 {
|
||||
c.ResponseError("Invalid phone numbers", invalidReceivers)
|
||||
c.ResponseError(fmt.Sprintf("Invalid phone receivers: %s", invalidReceivers))
|
||||
return
|
||||
}
|
||||
|
||||
client.SendMessage(smsForm.Parameters, smsForm.Receivers...)
|
||||
c.Data["json"] = Response{Status: "ok"}
|
||||
c.ServeJSON()
|
||||
err = object.SendSms(provider, smsForm.Content, smsForm.Receivers...)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.ResponseOk()
|
||||
}
|
||||
|
@@ -15,45 +15,14 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"golang.org/x/net/proxy"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
var defaultHttpClient *http.Client
|
||||
var proxyHttpClient *http.Client
|
||||
|
||||
func InitHttpClient() {
|
||||
// not use proxy
|
||||
defaultHttpClient = http.DefaultClient
|
||||
|
||||
// use proxy
|
||||
httpProxy := beego.AppConfig.String("httpProxy")
|
||||
if httpProxy == "" {
|
||||
proxyHttpClient = &http.Client{}
|
||||
return
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
|
||||
dialer, err := proxy.SOCKS5("tcp", httpProxy, nil, proxy.Direct)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tr := &http.Transport{Dial: dialer.Dial}
|
||||
proxyHttpClient = &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
//resp, err2 := proxyHttpClient.Get("https://google.com")
|
||||
//if err2 != nil {
|
||||
// panic(err2)
|
||||
//}
|
||||
//defer resp.Body.Close()
|
||||
//println("Response status: %s", resp.Status)
|
||||
}
|
||||
|
||||
// ResponseOk ...
|
||||
func (c *ApiController) ResponseOk(data ...interface{}) {
|
||||
resp := Response{Status: "ok"}
|
||||
@@ -91,3 +60,43 @@ func (c *ApiController) RequireSignedIn() (string, bool) {
|
||||
}
|
||||
return userId, true
|
||||
}
|
||||
|
||||
func getInitScore() int {
|
||||
score, err := strconv.Atoi(beego.AppConfig.String("initScore"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
|
||||
func (c *ApiController) GetProviderFromContext(category string) (*object.Provider, *object.User, bool) {
|
||||
providerName := c.Input().Get("provider")
|
||||
if providerName != "" {
|
||||
provider := object.GetProvider(util.GetId(providerName))
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf("The provider: %s is not found", providerName))
|
||||
return nil, nil, false
|
||||
}
|
||||
return provider, nil, true
|
||||
}
|
||||
|
||||
userId, ok := c.RequireSignedIn()
|
||||
if !ok {
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
application, user := object.GetApplicationByUserId(userId)
|
||||
if application == nil {
|
||||
c.ResponseError(fmt.Sprintf("No application is found for userId: \"%s\"", userId))
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
provider := application.GetProviderByCategory(category)
|
||||
if provider == nil {
|
||||
c.ResponseError(fmt.Sprintf("No provider for category: \"%s\" is found for application: %s", category, application.Name))
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
return provider, user, true
|
||||
}
|
||||
|
@@ -15,6 +15,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -62,8 +63,8 @@ func (c *ApiController) SendVerificationCode() {
|
||||
user := c.getCurrentUser()
|
||||
organization := object.GetOrganization(orgId)
|
||||
application := object.GetApplicationByOrganizationName(organization.Name)
|
||||
|
||||
msg := "Invalid dest type."
|
||||
|
||||
sendResp := errors.New("Invalid dest type.")
|
||||
switch destType {
|
||||
case "email":
|
||||
if !util.IsEmailValid(dest) {
|
||||
@@ -72,7 +73,7 @@ func (c *ApiController) SendVerificationCode() {
|
||||
}
|
||||
|
||||
provider := application.GetEmailProvider()
|
||||
msg = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
sendResp = object.SendVerificationCodeToEmail(organization, user, provider, remoteAddr, dest)
|
||||
case "phone":
|
||||
if !util.IsPhoneCnValid(dest) {
|
||||
c.ResponseError("Invalid phone number")
|
||||
@@ -86,15 +87,15 @@ func (c *ApiController) SendVerificationCode() {
|
||||
|
||||
dest = fmt.Sprintf("+%s%s", org.PhonePrefix, dest)
|
||||
provider := application.GetSmsProvider()
|
||||
msg = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
|
||||
sendResp = object.SendVerificationCodeToPhone(organization, user, provider, remoteAddr, dest)
|
||||
}
|
||||
|
||||
status := "ok"
|
||||
if msg != "" {
|
||||
if sendResp != nil {
|
||||
status = "error"
|
||||
}
|
||||
|
||||
c.Data["json"] = Response{Status: status, Msg: msg}
|
||||
c.Data["json"] = Response{Status: status, Msg: sendResp.Error()}
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
|
5
go.mod
5
go.mod
@@ -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.3.1
|
||||
github.com/casdoor/go-sms-sender v0.0.3
|
||||
github.com/casdoor/go-sms-sender v0.0.4
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||
@@ -18,6 +18,7 @@ require (
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/jinzhu/configor v1.2.1 // indirect
|
||||
github.com/mileusna/crontab v1.0.1
|
||||
github.com/plutov/paypal/v4 v4.3.7
|
||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||
github.com/qor/oss v0.0.0-20191031055114-aef9ba66bf76
|
||||
github.com/satori/go.uuid v1.2.0 // indirect
|
||||
@@ -30,5 +31,5 @@ require (
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
|
||||
gopkg.in/ini.v1 v1.62.0 // indirect
|
||||
xorm.io/core v0.7.2
|
||||
xorm.io/xorm v1.0.3
|
||||
xorm.io/xorm v1.0.6
|
||||
)
|
||||
|
10
go.sum
10
go.sum
@@ -75,8 +75,8 @@ github.com/casbin/casbin/v2 v2.30.1 h1:P5HWadDL7olwUXNdcuKUBk+x75Y2eitFxYTcLNKeK
|
||||
github.com/casbin/casbin/v2 v2.30.1/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
github.com/casbin/xorm-adapter/v2 v2.3.1 h1:RVGsM6KYFP9s4OQJXrP/gv56Wmt5P40mzvcyXgv5xeg=
|
||||
github.com/casbin/xorm-adapter/v2 v2.3.1/go.mod h1:GZ+nlIdasVFunQ71SlvkL/HcQQBvFncphDf+2Yl167c=
|
||||
github.com/casdoor/go-sms-sender v0.0.3 h1:17/dzAP/ZgSY4AORzcsR/48AKyBycQcHUGg00R9tnSI=
|
||||
github.com/casdoor/go-sms-sender v0.0.3/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
|
||||
github.com/casdoor/go-sms-sender v0.0.4 h1:UekC70YueeA5E2LrKJVQKCGntdTlYwal/7og4vao66U=
|
||||
github.com/casdoor/go-sms-sender v0.0.4/go.mod h1:TMM/BsZQAa+7JVDXl2KqgxnzZgCjmHEX5MBN662mM5M=
|
||||
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=
|
||||
@@ -256,6 +256,8 @@ github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHu
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/plutov/paypal/v4 v4.3.7 h1:wPvhAJ3RkDkV+UDrGX/UivXAl5JEPOOJuzsdgnTMJHc=
|
||||
github.com/plutov/paypal/v4 v4.3.7/go.mod h1:D56boafCRGcF/fEM0w282kj0fCDKIyrwOPX/Te1jCmw=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
@@ -300,6 +302,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
@@ -622,5 +625,6 @@ xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
|
||||
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=
|
||||
xorm.io/core v0.7.2/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
|
||||
xorm.io/xorm v1.0.3 h1:3dALAohvINu2mfEix5a5x5ZmSVGSljinoSGgvGbaZp0=
|
||||
xorm.io/xorm v1.0.3/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
||||
xorm.io/xorm v1.0.6 h1:7eco1c8QUpGz+3dztpLDj9gU1bTiQdFC/KtmPaLxUJk=
|
||||
xorm.io/xorm v1.0.6/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
|
||||
|
230
idp/gitlab.go
Normal file
230
idp/gitlab.go
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright 2021 The casbin 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"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
type GitlabIdProvider struct {
|
||||
Client *http.Client
|
||||
Config *oauth2.Config
|
||||
}
|
||||
|
||||
func NewGitlabIdProvider(clientId string, clientSecret string, redirectUrl string) *GitlabIdProvider {
|
||||
idp := &GitlabIdProvider{}
|
||||
|
||||
config := idp.getConfig(clientId, clientSecret, redirectUrl)
|
||||
idp.Config = config
|
||||
|
||||
return idp
|
||||
}
|
||||
|
||||
func (idp *GitlabIdProvider) SetHttpClient(client *http.Client) {
|
||||
idp.Client = client
|
||||
}
|
||||
|
||||
// getConfig return a point of Config, which describes a typical 3-legged OAuth2 flow
|
||||
func (idp *GitlabIdProvider) getConfig(clientId string, clientSecret string, redirectUrl string) *oauth2.Config {
|
||||
var endpoint = oauth2.Endpoint{
|
||||
TokenURL: "https://gitlab.com/oauth/token",
|
||||
}
|
||||
|
||||
var config = &oauth2.Config{
|
||||
Scopes: []string{"read_user+profile"},
|
||||
Endpoint: endpoint,
|
||||
ClientID: clientId,
|
||||
ClientSecret: clientSecret,
|
||||
RedirectURL: redirectUrl,
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type GitlabProviderToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
CreatedAt int `json:"created_at"`
|
||||
}
|
||||
|
||||
// GetToken use code get access_token (*operation of getting code ought to be done in front)
|
||||
// get more detail via: https://docs.gitlab.com/ee/api/oauth2.html
|
||||
func (idp *GitlabIdProvider) GetToken(code string) (*oauth2.Token, error) {
|
||||
params := url.Values{}
|
||||
params.Add("grant_type", "authorization_code")
|
||||
params.Add("client_id", idp.Config.ClientID)
|
||||
params.Add("client_secret", idp.Config.ClientSecret)
|
||||
params.Add("code", code)
|
||||
params.Add("redirect_uri", idp.Config.RedirectURL)
|
||||
|
||||
accessTokenUrl := fmt.Sprintf("%s?%s", idp.Config.Endpoint.TokenURL, params.Encode())
|
||||
resp, err := idp.Client.Post(accessTokenUrl, "application/json;charset=UTF-8", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
gtoken := &GitlabProviderToken{}
|
||||
if err = json.Unmarshal(data, gtoken); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// gtoken.ExpiresIn always returns 0, so we set Expiry=7200 to avoid verification errors.
|
||||
token := &oauth2.Token{
|
||||
AccessToken: gtoken.AccessToken,
|
||||
TokenType: gtoken.TokenType,
|
||||
RefreshToken: gtoken.RefreshToken,
|
||||
Expiry: time.Unix(time.Now().Unix()+int64(7200), 0),
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
/*
|
||||
{
|
||||
"id":5162115,
|
||||
"name":"shiluo",
|
||||
"username":"shiluo",
|
||||
"state":"active",
|
||||
"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/5162115/avatar.png",
|
||||
"web_url":"https://gitlab.com/shiluo",
|
||||
"created_at":"2019-12-23T02:50:10.348Z",
|
||||
"bio":"",
|
||||
"bio_html":"",
|
||||
"location":"China",
|
||||
"public_email":"silo1999@163.com",
|
||||
"skype":"",
|
||||
"linkedin":"",
|
||||
"twitter":"",
|
||||
"website_url":"",
|
||||
"organization":"",
|
||||
"job_title":"",
|
||||
"pronouns":null,
|
||||
"bot":false,
|
||||
"work_information":null,
|
||||
"followers":0,
|
||||
"following":0,
|
||||
"last_sign_in_at":"2019-12-26T13:24:42.941Z",
|
||||
"confirmed_at":"2019-12-23T02:52:10.778Z",
|
||||
"last_activity_on":"2021-08-19",
|
||||
"email":"silo1999@163.com",
|
||||
"theme_id":1,
|
||||
"color_scheme_id":1,
|
||||
"projects_limit":100000,
|
||||
"current_sign_in_at":"2021-08-19T09:46:46.004Z",
|
||||
"identities":[
|
||||
{
|
||||
"provider":"github",
|
||||
"extern_uid":"51157931",
|
||||
"saml_provider_id":null
|
||||
}
|
||||
],
|
||||
"can_create_group":true,
|
||||
"can_create_project":true,
|
||||
"two_factor_enabled":false,
|
||||
"external":false,
|
||||
"private_profile":false,
|
||||
"commit_email":"silo1999@163.com",
|
||||
"shared_runners_minutes_limit":null,
|
||||
"extra_shared_runners_minutes_limit":null
|
||||
}
|
||||
*/
|
||||
|
||||
type GitlabUserInfo struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Username string `json:"username"`
|
||||
State string `json:"state"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
WebUrl string `json:"web_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Bio string `json:"bio"`
|
||||
BioHtml string `json:"bio_html"`
|
||||
Location string `json:"location"`
|
||||
PublicEmail string `json:"public_email"`
|
||||
Skype string `json:"skype"`
|
||||
Linkedin string `json:"linkedin"`
|
||||
Twitter string `json:"twitter"`
|
||||
WebsiteUrl string `json:"website_url"`
|
||||
Organization string `json:"organization"`
|
||||
JobTitle string `json:"job_title"`
|
||||
Pronouns interface{} `json:"pronouns"`
|
||||
Bot bool `json:"bot"`
|
||||
WorkInformation interface{} `json:"work_information"`
|
||||
Followers int `json:"followers"`
|
||||
Following int `json:"following"`
|
||||
LastSignInAt time.Time `json:"last_sign_in_at"`
|
||||
ConfirmedAt time.Time `json:"confirmed_at"`
|
||||
LastActivityOn string `json:"last_activity_on"`
|
||||
Email string `json:"email"`
|
||||
ThemeId int `json:"theme_id"`
|
||||
ColorSchemeId int `json:"color_scheme_id"`
|
||||
ProjectsLimit int `json:"projects_limit"`
|
||||
CurrentSignInAt time.Time `json:"current_sign_in_at"`
|
||||
Identities []struct {
|
||||
Provider string `json:"provider"`
|
||||
ExternUid string `json:"extern_uid"`
|
||||
SamlProviderId interface{} `json:"saml_provider_id"`
|
||||
} `json:"identities"`
|
||||
CanCreateGroup bool `json:"can_create_group"`
|
||||
CanCreateProject bool `json:"can_create_project"`
|
||||
TwoFactorEnabled bool `json:"two_factor_enabled"`
|
||||
External bool `json:"external"`
|
||||
PrivateProfile bool `json:"private_profile"`
|
||||
CommitEmail string `json:"commit_email"`
|
||||
SharedRunnersMinutesLimit interface{} `json:"shared_runners_minutes_limit"`
|
||||
ExtraSharedRunnersMinutesLimit interface{} `json:"extra_shared_runners_minutes_limit"`
|
||||
}
|
||||
|
||||
// GetUserInfo use GitlabProviderToken gotten before return GitlabUserInfo
|
||||
func (idp *GitlabIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
||||
resp, err := idp.Client.Get("https://gitlab.com/api/v4/user?access_token="+token.AccessToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
guser := GitlabUserInfo{}
|
||||
if err = json.Unmarshal(data, &guser);err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfo := UserInfo{
|
||||
Id: strconv.Itoa(guser.Id),
|
||||
Username: guser.Username,
|
||||
DisplayName: guser.Name,
|
||||
AvatarUrl: guser.AvatarUrl,
|
||||
Email: guser.Email,
|
||||
}
|
||||
return &userInfo, nil
|
||||
}
|
@@ -57,6 +57,8 @@ func GetIdProvider(providerType string, clientId string, clientSecret string, re
|
||||
return NewWeComIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "Lark" {
|
||||
return NewLarkIdProvider(clientId, clientSecret, redirectUrl)
|
||||
} else if providerType == "GitLab" {
|
||||
return NewGitlabIdProvider(clientId, clientSecret, redirectUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
5
main.go
5
main.go
@@ -20,8 +20,8 @@ import (
|
||||
"github.com/astaxie/beego/plugins/cors"
|
||||
_ "github.com/astaxie/beego/session/redis"
|
||||
"github.com/casbin/casdoor/authz"
|
||||
"github.com/casbin/casdoor/controllers"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/proxy"
|
||||
"github.com/casbin/casdoor/routers"
|
||||
|
||||
_ "github.com/casbin/casdoor/routers"
|
||||
@@ -30,7 +30,8 @@ import (
|
||||
func main() {
|
||||
object.InitAdapter()
|
||||
object.InitDb()
|
||||
controllers.InitHttpClient()
|
||||
object.InitDefaultStorageProvider()
|
||||
proxy.InitHttpClient()
|
||||
authz.InitAuthz()
|
||||
|
||||
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
|
||||
|
@@ -147,4 +147,9 @@ func (a *Adapter) createTable() {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = a.Engine.Sync2(new(Payment))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
@@ -127,6 +127,21 @@ func GetApplicationByUser(user *User) *Application {
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationByUserId(userId string) (*Application, *User) {
|
||||
var application *Application
|
||||
|
||||
owner, name := util.GetOwnerAndNameFromId(userId)
|
||||
if owner == "app" {
|
||||
application = getApplication("admin", name)
|
||||
return application, nil
|
||||
}
|
||||
|
||||
user := GetUser(userId)
|
||||
application = GetApplicationByUser(user)
|
||||
|
||||
return application, user
|
||||
}
|
||||
|
||||
func GetApplicationByClientId(clientId string) *Application {
|
||||
application := Application{}
|
||||
existed, err := adapter.Engine.Where("client_id=?", clientId).Get(&application)
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
package object
|
||||
|
||||
func (application *Application) getProviderByCategory(category string) *Provider {
|
||||
func (application *Application) GetProviderByCategory(category string) *Provider {
|
||||
providers := GetProviders(application.Owner)
|
||||
m := map[string]*Provider{}
|
||||
for _, provider := range providers {
|
||||
@@ -35,15 +35,19 @@ func (application *Application) getProviderByCategory(category string) *Provider
|
||||
}
|
||||
|
||||
func (application *Application) GetEmailProvider() *Provider {
|
||||
return application.getProviderByCategory("Email")
|
||||
return application.GetProviderByCategory("Email")
|
||||
}
|
||||
|
||||
func (application *Application) GetSmsProvider() *Provider {
|
||||
return application.getProviderByCategory("SMS")
|
||||
return application.GetProviderByCategory("SMS")
|
||||
}
|
||||
|
||||
func (application *Application) GetStorageProvider() *Provider {
|
||||
return application.getProviderByCategory("Storage")
|
||||
return application.GetProviderByCategory("Storage")
|
||||
}
|
||||
|
||||
func (application *Application) GetPayProvider() *Provider {
|
||||
return application.GetProviderByCategory("Pay")
|
||||
}
|
||||
|
||||
func (application *Application) getSignupItem(itemName string) *SignupItem {
|
||||
|
72
object/avatar.go
Normal file
72
object/avatar.go
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/proxy"
|
||||
)
|
||||
|
||||
var defaultStorageProvider *Provider = nil
|
||||
|
||||
func InitDefaultStorageProvider() {
|
||||
defaultStorageProviderStr := beego.AppConfig.String("defaultStorageProvider")
|
||||
if defaultStorageProviderStr != "" {
|
||||
defaultStorageProvider = getProvider("admin", defaultStorageProviderStr)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadFile(url string) (*bytes.Buffer, error) {
|
||||
httpClient := proxy.GetHttpClient(url)
|
||||
|
||||
resp, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fileBuffer := bytes.NewBuffer(nil)
|
||||
_, err = io.Copy(fileBuffer, resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fileBuffer, nil
|
||||
}
|
||||
|
||||
func getPermanentAvatarUrl(organization string, username string, url string) string {
|
||||
if defaultStorageProvider == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
fullFilePath := fmt.Sprintf("/avatar/%s/%s.png", organization, username)
|
||||
uploadedFileUrl, _ := getUploadFileUrl(defaultStorageProvider, fullFilePath, false)
|
||||
|
||||
fileBuffer, err := downloadFile(url)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, _, err = UploadFile(defaultStorageProvider, fullFilePath, fileBuffer)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return uploadedFileUrl
|
||||
}
|
@@ -12,23 +12,28 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package routers
|
||||
package object
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/casbin/casdoor/proxy"
|
||||
)
|
||||
|
||||
func parseQuery(query string, key string) string {
|
||||
queryMap, err := url.ParseQuery(query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
func TestSyncPermanentAvatars(t *testing.T) {
|
||||
InitConfig()
|
||||
InitDefaultStorageProvider()
|
||||
proxy.InitHttpClient()
|
||||
|
||||
users := GetGlobalUsers()
|
||||
for i, user := range users {
|
||||
if user.Avatar == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
updateUserColumn("permanent_avatar", user)
|
||||
fmt.Printf("[%d/%d]: Update user: [%s]'s permanent avatar: %s\n", i, len(users), user.GetId(), user.PermanentAvatar)
|
||||
}
|
||||
|
||||
return queryMap.Get(key)
|
||||
}
|
||||
|
||||
func parseSlash(s string) (string, string) {
|
||||
tokens := strings.Split(s, "/")
|
||||
return tokens[0], tokens[1]
|
||||
}
|
@@ -83,19 +83,20 @@ func CheckUserSignup(application *Application, organization *Organization, usern
|
||||
|
||||
func CheckPassword(user *User, password string) string {
|
||||
organization := GetOrganizationByUser(user)
|
||||
|
||||
if organization == nil {
|
||||
return "organization does not exist"
|
||||
}
|
||||
|
||||
if organization.PasswordType == "plain" {
|
||||
if password == user.Password {
|
||||
return ""
|
||||
} else {
|
||||
return "password incorrect"
|
||||
}
|
||||
return "password incorrect"
|
||||
} else if organization.PasswordType == "salt" {
|
||||
if password == user.Password || getSaltedPassword(password, organization.PasswordSalt) == user.Password {
|
||||
return ""
|
||||
} else {
|
||||
return "password incorrect"
|
||||
}
|
||||
return "password incorrect"
|
||||
} else {
|
||||
return fmt.Sprintf("unsupported password type: %s", organization.PasswordType)
|
||||
}
|
||||
|
@@ -18,7 +18,7 @@ package object
|
||||
|
||||
import "github.com/go-gomail/gomail"
|
||||
|
||||
func SendEmail(provider *Provider, title, content, dest, sender string) string {
|
||||
func SendEmail(provider *Provider, title string, content string, dest string, sender string) error {
|
||||
dialer := gomail.NewDialer(provider.Host, provider.Port, provider.ClientId, provider.ClientSecret)
|
||||
|
||||
message := gomail.NewMessage()
|
||||
@@ -27,10 +27,5 @@ func SendEmail(provider *Provider, title, content, dest, sender string) string {
|
||||
message.SetHeader("Subject", title)
|
||||
message.SetBody("text/html", content)
|
||||
|
||||
err := dialer.DialAndSend(message)
|
||||
if err == nil {
|
||||
return ""
|
||||
} else {
|
||||
return err.Error()
|
||||
}
|
||||
return dialer.DialAndSend(message)
|
||||
}
|
||||
|
@@ -63,6 +63,7 @@ func initBuiltInUser() {
|
||||
Address: []string{},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Score: 2000,
|
||||
IsAdmin: true,
|
||||
IsGlobalAdmin: true,
|
||||
IsForbidden: false,
|
||||
|
@@ -305,6 +305,7 @@ func SyncLdapUsers(owner string, users []LdapRespUser) (*[]LdapRespUser, *[]Ldap
|
||||
Address: []string{user.Address},
|
||||
Affiliation: "Example Inc.",
|
||||
Tag: "staff",
|
||||
Score: 2000,
|
||||
Ldap: user.Uuid,
|
||||
}) {
|
||||
failedUsers = append(failedUsers, user)
|
||||
|
@@ -56,9 +56,9 @@ func getOrganization(owner string, name string) *Organization {
|
||||
|
||||
if existed {
|
||||
return &organization
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetOrganization(id string) *Organization {
|
||||
|
8
object/pay_item.go
Normal file
8
object/pay_item.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package object
|
||||
|
||||
type PayItem struct {
|
||||
Invoice string `json:"invoice"`
|
||||
Price string `json:"price"`
|
||||
Description string `json:"description"`
|
||||
Currency string `json:"currency"`
|
||||
}
|
74
object/payment.go
Normal file
74
object/payment.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"github.com/plutov/paypal/v4"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Payment struct {
|
||||
Id string `xorm:"varchar(100) notnull pk" json:"id"`
|
||||
Invoice string `xorm:"varchar(100)" json:"invoice"`
|
||||
Application string `xorm:"varchar(100)" json:"application"`
|
||||
PayItem PayItem `xorm:"json varchar(1000)" json:"pay_item"`
|
||||
Payer *paypal.PayerWithNameAndPhone `xorm:"json varchar(1000)" json:"payer"`
|
||||
Purchase []paypal.CapturedPurchaseUnit `xorm:"varchar(10000)" json:"purchase"`
|
||||
Status string `xorm:"varchar(100)" json:"status"`
|
||||
CreateTime string `xorm:"varchar(100) created" json:"create_time"`
|
||||
UpdateTime string `xorm:"varchar(100) updated" json:"update_time"`
|
||||
Callback string `xorm:"varchar(1000)" json:"callback"`
|
||||
}
|
||||
|
||||
func AddPayment(pay *Payment) bool {
|
||||
affected, err := adapter.Engine.Insert(pay)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func GetPayments() []*Payment {
|
||||
pays := []*Payment{}
|
||||
err := adapter.Engine.Desc("create_time").Find(&pays)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return pays
|
||||
}
|
||||
|
||||
func GetPayment(id string) *Payment {
|
||||
pay := Payment{Id: id}
|
||||
existed, err := adapter.Engine.Get(&pay)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if existed {
|
||||
return &pay
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func DeletePayment(payment *Payment) bool {
|
||||
affected, err := adapter.Engine.ID(core.PK{payment.Id}).Delete(&Payment{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func UpdatePay(id string, pay *Payment) bool {
|
||||
if GetPayment(id) == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{id}).AllCols().Update(pay)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return affected != 0
|
||||
}
|
@@ -29,6 +29,7 @@ type Provider struct {
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Category string `xorm:"varchar(100)" json:"category"`
|
||||
Type string `xorm:"varchar(100)" json:"type"`
|
||||
Method string `xorm:"varchar(100)" json:"method"`
|
||||
ClientId string `xorm:"varchar(100)" json:"clientId"`
|
||||
ClientSecret string `xorm:"varchar(100)" json:"clientSecret"`
|
||||
|
||||
@@ -57,6 +58,7 @@ func getMaskedProvider(provider *Provider) *Provider {
|
||||
DisplayName: provider.DisplayName,
|
||||
Category: provider.Category,
|
||||
Type: provider.Type,
|
||||
Method: provider.Method,
|
||||
ClientId: provider.ClientId,
|
||||
}
|
||||
return p
|
||||
|
@@ -16,9 +16,9 @@ package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"xorm.io/core"
|
||||
|
||||
"github.com/casbin/casdoor/util"
|
||||
"xorm.io/core"
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
@@ -26,19 +26,26 @@ type Resource struct {
|
||||
Name string `xorm:"varchar(100) notnull pk" json:"name"`
|
||||
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
|
||||
|
||||
Provider string `xorm:"varchar(100)" json:"provider"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Parent string `xorm:"varchar(100)" json:"parent"`
|
||||
FileType string `xorm:"varchar(100)" json:"fileType"`
|
||||
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
||||
FileSize int `json:"fileSize"`
|
||||
Url string `xorm:"varchar(100)" json:"url"`
|
||||
ObjectKey string `xorm:"varchar(100)" json:"objectKey"`
|
||||
User string `xorm:"varchar(100)" json:"user"`
|
||||
Provider string `xorm:"varchar(100)" json:"provider"`
|
||||
Application string `xorm:"varchar(100)" json:"application"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Parent string `xorm:"varchar(100)" json:"parent"`
|
||||
FileName string `xorm:"varchar(100)" json:"fileName"`
|
||||
FileType string `xorm:"varchar(100)" json:"fileType"`
|
||||
FileFormat string `xorm:"varchar(100)" json:"fileFormat"`
|
||||
FileSize int `json:"fileSize"`
|
||||
Url string `xorm:"varchar(100)" json:"url"`
|
||||
}
|
||||
|
||||
func GetResources(owner string) []*Resource {
|
||||
func GetResources(owner string, user string) []*Resource {
|
||||
if owner == "built-in" {
|
||||
owner = ""
|
||||
user = ""
|
||||
}
|
||||
|
||||
resources := []*Resource{}
|
||||
err := adapter.Engine.Desc("created_time").Find(&resources, &Resource{Owner: owner})
|
||||
err := adapter.Engine.Desc("created_time").Find(&resources, &Resource{Owner: owner, User: user})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -55,18 +62,18 @@ func getResource(owner string, name string) *Resource {
|
||||
|
||||
if existed {
|
||||
return &resource
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetResource(id string) *Resource {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
return getResource(owner, name)
|
||||
}
|
||||
|
||||
func UpdateResource(id string, resource *Resource) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
owner, name := util.GetOwnerAndNameFromIdNoCheck(id)
|
||||
if getResource(owner, name) == nil {
|
||||
return false
|
||||
}
|
||||
|
@@ -14,24 +14,21 @@
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
import "github.com/casdoor/go-sms-sender"
|
||||
|
||||
"github.com/casdoor/go-sms-sender"
|
||||
)
|
||||
|
||||
func SendCodeToPhone(provider *Provider, phone, code string) string {
|
||||
client := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.RegionId, provider.TemplateCode, provider.AppId)
|
||||
if client == nil {
|
||||
return fmt.Sprintf("Unsupported provide type: %s", provider.Type)
|
||||
func SendSms(provider *Provider, content string, phoneNumbers ...string) error {
|
||||
client, err := go_sms_sender.NewSmsClient(provider.Type, provider.ClientId, provider.ClientSecret, provider.SignName, provider.RegionId, provider.TemplateCode, provider.AppId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
param := make(map[string]string)
|
||||
params := map[string]string{}
|
||||
if provider.Type == go_sms_sender.TencentCloud {
|
||||
param["0"] = code
|
||||
params["0"] = content
|
||||
} else {
|
||||
param["code"] = code
|
||||
params["code"] = content
|
||||
}
|
||||
client.SendMessage(param, phone)
|
||||
return ""
|
||||
|
||||
err = client.SendMessage(params, phoneNumbers...)
|
||||
return err
|
||||
}
|
||||
|
@@ -23,22 +23,8 @@ import (
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
func UploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
|
||||
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
|
||||
if storageProvider == nil {
|
||||
return "", "", fmt.Errorf("the provider type: %s is not supported", provider.Type)
|
||||
}
|
||||
|
||||
if provider.Domain == "" {
|
||||
provider.Domain = storageProvider.GetEndpoint()
|
||||
UpdateProvider(provider.GetId(), provider)
|
||||
}
|
||||
|
||||
func getUploadFileUrl(provider *Provider, fullFilePath string, hasTimestamp bool) (string, string) {
|
||||
objectKey := util.UrlJoin(util.GetUrlPath(provider.Domain), fullFilePath)
|
||||
_, err := storageProvider.Put(objectKey, fileBuffer)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
host := ""
|
||||
if provider.Type != "Local File System" {
|
||||
@@ -52,7 +38,32 @@ func UploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffe
|
||||
host = util.UrlJoin(provider.Domain, "/files")
|
||||
}
|
||||
|
||||
fileUrl := fmt.Sprintf("%s?time=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime())
|
||||
fileUrl := util.UrlJoin(host, objectKey)
|
||||
if hasTimestamp {
|
||||
fileUrl = fmt.Sprintf("%s?t=%s", util.UrlJoin(host, objectKey), util.GetCurrentUnixTime())
|
||||
}
|
||||
|
||||
return fileUrl, objectKey
|
||||
}
|
||||
|
||||
func UploadFile(provider *Provider, fullFilePath string, fileBuffer *bytes.Buffer) (string, string, error) {
|
||||
storageProvider := storage.GetStorageProvider(provider.Type, provider.ClientId, provider.ClientSecret, provider.RegionId, provider.Bucket, provider.Endpoint)
|
||||
if storageProvider == nil {
|
||||
return "", "", fmt.Errorf("the provider type: %s is not supported", provider.Type)
|
||||
}
|
||||
|
||||
if provider.Domain == "" {
|
||||
provider.Domain = storageProvider.GetEndpoint()
|
||||
UpdateProvider(provider.GetId(), provider)
|
||||
}
|
||||
|
||||
fileUrl, objectKey := getUploadFileUrl(provider, fullFilePath, true)
|
||||
|
||||
_, err := storageProvider.Put(objectKey, fileBuffer)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return fileUrl, objectKey, nil
|
||||
}
|
||||
|
||||
|
@@ -72,9 +72,9 @@ func getToken(owner string, name string) *Token {
|
||||
|
||||
if existed {
|
||||
return &token
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTokenByCode(code string) *Token {
|
||||
@@ -86,9 +86,9 @@ func getTokenByCode(code string) *Token {
|
||||
|
||||
if existed {
|
||||
return &token
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetToken(id string) *Token {
|
||||
|
@@ -32,14 +32,21 @@ type User struct {
|
||||
Password string `xorm:"varchar(100)" json:"password"`
|
||||
DisplayName string `xorm:"varchar(100)" json:"displayName"`
|
||||
Avatar string `xorm:"varchar(255)" json:"avatar"`
|
||||
PermanentAvatar string `xorm:"varchar(255)" json:"permanentAvatar"`
|
||||
Email string `xorm:"varchar(100)" json:"email"`
|
||||
Phone string `xorm:"varchar(100)" json:"phone"`
|
||||
Location string `xorm:"varchar(100)" json:"location"`
|
||||
Address []string `json:"address"`
|
||||
Affiliation string `xorm:"varchar(100)" json:"affiliation"`
|
||||
Title string `xorm:"varchar(100)" json:"title"`
|
||||
Homepage string `xorm:"varchar(100)" json:"homepage"`
|
||||
Bio string `xorm:"varchar(100)" json:"bio"`
|
||||
Tag string `xorm:"varchar(100)" json:"tag"`
|
||||
Region string `xorm:"varchar(100)" json:"region"`
|
||||
Language string `xorm:"varchar(100)" json:"language"`
|
||||
Score int `json:"score"`
|
||||
Ranking int `json:"ranking"`
|
||||
IsOnline bool `json:"isOnline"`
|
||||
IsAdmin bool `json:"isAdmin"`
|
||||
IsGlobalAdmin bool `json:"isGlobalAdmin"`
|
||||
IsForbidden bool `json:"isForbidden"`
|
||||
@@ -58,6 +65,7 @@ type User struct {
|
||||
LinkedIn string `xorm:"linkedin varchar(100)" json:"linkedin"`
|
||||
Wecom string `xorm:"wecom varchar(100)" json:"wecom"`
|
||||
Lark string `xorm:"lark varchar(100)" json:"lark"`
|
||||
Gitlab string `xorm:"gitlab varchar(100)" json:"gitlab"`
|
||||
|
||||
Ldap string `xorm:"ldap varchar(100)" json:"ldap"`
|
||||
Properties map[string]string `json:"properties"`
|
||||
@@ -140,14 +148,19 @@ func GetLastUser(owner string) *User {
|
||||
|
||||
func UpdateUser(id string, user *User) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getUser(owner, name) == nil {
|
||||
oldUser := getUser(owner, name)
|
||||
if oldUser == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
user.UpdateUserHash()
|
||||
|
||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).Cols("owner", "display_name", "avatar",
|
||||
"address", "region", "language", "affiliation", "score", "tag", "is_admin", "is_global_admin", "is_forbidden",
|
||||
"location", "address", "region", "language", "affiliation", "title", "homepage", "bio", "score", "tag", "is_admin", "is_global_admin", "is_forbidden",
|
||||
"hash", "properties").Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -156,14 +169,19 @@ func UpdateUser(id string, user *User) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func UpdateUserInternal(id string, user *User) bool {
|
||||
func UpdateUserForAllFields(id string, user *User) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(id)
|
||||
if getUser(owner, name) == nil {
|
||||
oldUser := getUser(owner, name)
|
||||
if oldUser == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
user.UpdateUserHash()
|
||||
|
||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -172,7 +190,17 @@ func UpdateUserInternal(id string, user *User) bool {
|
||||
return affected != 0
|
||||
}
|
||||
|
||||
func UpdateUserForOriginal(user *User) bool {
|
||||
func UpdateUserForOriginalFields(user *User) bool {
|
||||
owner, name := util.GetOwnerAndNameFromId(user.GetId())
|
||||
oldUser := getUser(owner, name)
|
||||
if oldUser == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if user.Avatar != oldUser.Avatar && user.Avatar != "" {
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.ID(core.PK{user.Owner, user.Name}).Cols("display_name", "password", "phone", "avatar", "affiliation", "score", "is_forbidden", "hash", "pre_hash").Update(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -192,6 +220,8 @@ func AddUser(user *User) bool {
|
||||
user.UpdateUserHash()
|
||||
user.PreHash = user.Hash
|
||||
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
|
||||
affected, err := adapter.Engine.Insert(user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -211,6 +241,8 @@ func AddUsers(users []*User) bool {
|
||||
|
||||
user.UpdateUserHash()
|
||||
user.PreHash = user.Hash
|
||||
|
||||
user.PermanentAvatar = getPermanentAvatarUrl(user.Owner, user.Name, user.Avatar)
|
||||
}
|
||||
|
||||
affected, err := adapter.Engine.Insert(users)
|
||||
@@ -237,7 +269,8 @@ func AddUsersSafe(users []*User) bool {
|
||||
}
|
||||
|
||||
tmp := users[start:end]
|
||||
fmt.Printf("Add users: [%d - %d].\n", start, end)
|
||||
// TODO: save to log instead of standard output
|
||||
// fmt.Printf("Add users: [%d - %d].\n", start, end)
|
||||
if AddUsers(tmp) {
|
||||
affected = true
|
||||
}
|
||||
|
@@ -91,8 +91,8 @@ func TestGetMaskedUsers(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "1",
|
||||
args: args{users: []*User{{Password: "casdoor"},{Password: "casbin"}}},
|
||||
want: []*User{{Password: "***"},{Password: "***"}},
|
||||
args: args{users: []*User{{Password: "casdoor"}, {Password: "casbin"}}},
|
||||
want: []*User{{Password: "***"}, {Password: "***"}},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -102,4 +102,4 @@ func TestGetMaskedUsers(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -135,7 +135,7 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
|
||||
}
|
||||
}
|
||||
|
||||
affected := UpdateUserInternal(user.GetId(), user)
|
||||
affected := UpdateUserForAllFields(user.GetId(), user)
|
||||
return affected
|
||||
}
|
||||
|
||||
|
@@ -15,6 +15,7 @@
|
||||
package object
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
@@ -39,9 +40,9 @@ type VerificationRecord struct {
|
||||
IsUsed bool
|
||||
}
|
||||
|
||||
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) string {
|
||||
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
|
||||
if provider == nil {
|
||||
return "Please set an Email provider first"
|
||||
return fmt.Errorf("Please set an Email provider first")
|
||||
}
|
||||
|
||||
sender := organization.DisplayName
|
||||
@@ -50,27 +51,27 @@ func SendVerificationCodeToEmail(organization *Organization, user *User, provide
|
||||
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
|
||||
content := fmt.Sprintf(provider.Content, code)
|
||||
|
||||
if result := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); len(result) != 0 {
|
||||
return result
|
||||
if err := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return SendEmail(provider, title, content, dest, sender)
|
||||
}
|
||||
|
||||
func SendVerificationCodeToPhone(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) string {
|
||||
func SendVerificationCodeToPhone(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
|
||||
if provider == nil {
|
||||
return "Please set a SMS provider first"
|
||||
return errors.New("Please set a SMS provider first")
|
||||
}
|
||||
|
||||
code := getRandomCode(5)
|
||||
if result := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); len(result) != 0 {
|
||||
return result
|
||||
if err := AddToVerificationRecord(user, provider, remoteAddr, provider.Category, dest, code); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return SendCodeToPhone(provider, dest, code)
|
||||
return SendSms(provider, dest, code)
|
||||
}
|
||||
|
||||
func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordType, dest, code string) string {
|
||||
func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordType, dest, code string) error {
|
||||
var record VerificationRecord
|
||||
record.RemoteAddr = remoteAddr
|
||||
record.Type = recordType
|
||||
@@ -79,12 +80,12 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
|
||||
}
|
||||
has, err := adapter.Engine.Desc("created_time").Get(&record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
if has && now-record.Time < 60 {
|
||||
return "You can only send one code in 60s."
|
||||
return errors.New("You can only send one code in 60s.")
|
||||
}
|
||||
|
||||
record.Owner = provider.Owner
|
||||
@@ -102,10 +103,10 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
|
||||
|
||||
_, err = adapter.Engine.Insert(record)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func getVerificationRecord(dest string) *VerificationRecord {
|
||||
|
@@ -112,7 +112,7 @@ func syncUsers() {
|
||||
updatedUser := createUserFromOriginalUser(oUser, affiliationMap)
|
||||
updatedUser.Hash = oHash
|
||||
updatedUser.PreHash = oHash
|
||||
object.UpdateUserForOriginal(updatedUser)
|
||||
object.UpdateUserForOriginalFields(updatedUser)
|
||||
fmt.Printf("Update from oUser to user: %v\n", updatedUser)
|
||||
}
|
||||
} else {
|
||||
@@ -133,7 +133,7 @@ func syncUsers() {
|
||||
updatedUser := createUserFromOriginalUser(oUser, affiliationMap)
|
||||
updatedUser.Hash = oHash
|
||||
updatedUser.PreHash = oHash
|
||||
object.UpdateUserForOriginal(updatedUser)
|
||||
object.UpdateUserForOriginalFields(updatedUser)
|
||||
fmt.Printf("Update from oUser to user (2nd condition): %v\n", updatedUser)
|
||||
}
|
||||
}
|
||||
|
105
payment/paypal.go
Normal file
105
payment/paypal.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/plutov/paypal/v4"
|
||||
)
|
||||
|
||||
var client = GetClient()
|
||||
|
||||
func GetClient() *paypal.Client {
|
||||
c, err := paypal.NewClient(beego.AppConfig.String("paypalClientId"), beego.AppConfig.String("paypalSecret"), paypal.APIBaseSandBox)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func Paypal(payItem object.PayItem, clientId string, redirectUri string) string {
|
||||
|
||||
application := object.GetApplicationByClientId(clientId)
|
||||
if application == nil {
|
||||
return "Invalid client_id"
|
||||
}
|
||||
applicationName := fmt.Sprintf("%s/%s", application.Owner, application.Name)
|
||||
if payItem.Currency == "" {
|
||||
payItem.Currency = "USD"
|
||||
}
|
||||
|
||||
_, err := client.GetAccessToken(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
appContext := &paypal.ApplicationContext{
|
||||
ReturnURL: "http://localhost:7001/pay/success", //回调链接
|
||||
CancelURL: "https://www.baidu.com",
|
||||
}
|
||||
|
||||
purchaseUnits := make([]paypal.PurchaseUnitRequest, 1)
|
||||
purchaseUnits[0] = paypal.PurchaseUnitRequest{
|
||||
Amount: &paypal.PurchaseUnitAmount{
|
||||
Currency: payItem.Currency, //收款类型
|
||||
Value: payItem.Price, //收款数量
|
||||
},
|
||||
InvoiceID: payItem.Invoice,
|
||||
Description: payItem.Description,
|
||||
}
|
||||
|
||||
order, err := client.CreateOrder(context.Background(),
|
||||
paypal.OrderIntentCapture,
|
||||
purchaseUnits,
|
||||
&paypal.CreateOrderPayer{},
|
||||
appContext)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
newPay := object.Payment{
|
||||
Id: order.ID,
|
||||
Invoice: payItem.Invoice,
|
||||
PayItem: payItem,
|
||||
Application: applicationName,
|
||||
Status: order.Status,
|
||||
Callback: redirectUri,
|
||||
}
|
||||
|
||||
success := object.AddPayment(&newPay)
|
||||
if success {
|
||||
links := order.Links
|
||||
for _, link := range links {
|
||||
fmt.Println(link.Rel)
|
||||
if link.Rel == "approve" {
|
||||
return link.Href
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "Add Order to Database false"
|
||||
}
|
||||
|
||||
func SuccessPay(token string) string {
|
||||
_, err := client.GetAccessToken(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
captureOrder, err := client.CaptureOrder(context.Background(), token, paypal.CaptureOrderRequest{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
pay := object.GetPayment(captureOrder.ID)
|
||||
pay.Purchase = captureOrder.PurchaseUnits
|
||||
pay.Payer = captureOrder.Payer
|
||||
pay.UpdateTime = time.Now().String()
|
||||
pay.Status = captureOrder.Status
|
||||
object.UpdatePay(captureOrder.ID, pay)
|
||||
if captureOrder.Status == "COMPLETED" {
|
||||
return fmt.Sprintf("%s?paymentId=%s", pay.Callback, token)
|
||||
}
|
||||
return ""
|
||||
|
||||
}
|
64
proxy/proxy.go
Normal file
64
proxy/proxy.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2021 The casbin 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 proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
var DefaultHttpClient *http.Client
|
||||
var ProxyHttpClient *http.Client
|
||||
|
||||
func InitHttpClient() {
|
||||
// not use proxy
|
||||
DefaultHttpClient = http.DefaultClient
|
||||
|
||||
// use proxy
|
||||
httpProxy := beego.AppConfig.String("httpProxy")
|
||||
if httpProxy == "" {
|
||||
ProxyHttpClient = DefaultHttpClient
|
||||
return
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/questions/33585587/creating-a-go-socks5-client
|
||||
dialer, err := proxy.SOCKS5("tcp", httpProxy, nil, proxy.Direct)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tr := &http.Transport{Dial: dialer.Dial}
|
||||
ProxyHttpClient = &http.Client{
|
||||
Transport: tr,
|
||||
}
|
||||
|
||||
//resp, err2 := ProxyHttpClient.Get("https://google.com")
|
||||
//if err2 != nil {
|
||||
// panic(err2)
|
||||
//}
|
||||
//defer resp.Body.Close()
|
||||
//println("Response status: %s", resp.Status)
|
||||
}
|
||||
|
||||
func GetHttpClient(url string) *http.Client {
|
||||
if strings.Contains(url, "githubusercontent.com") {
|
||||
return ProxyHttpClient
|
||||
} else {
|
||||
return DefaultHttpClient
|
||||
}
|
||||
}
|
@@ -18,11 +18,9 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/authz"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
@@ -31,20 +29,6 @@ type Object struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func getUsernameByClientIdSecret(ctx *context.Context) string {
|
||||
clientId := ctx.Input.Query("clientId")
|
||||
clientSecret := ctx.Input.Query("clientSecret")
|
||||
if len(clientId) == 0 || len(clientSecret) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
app := object.GetApplicationByClientId(clientId)
|
||||
if app == nil || app.ClientSecret != clientSecret {
|
||||
return ""
|
||||
}
|
||||
return "built-in/service"
|
||||
}
|
||||
|
||||
func getUsername(ctx *context.Context) (username string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -52,11 +36,9 @@ func getUsername(ctx *context.Context) (username string) {
|
||||
}
|
||||
}()
|
||||
|
||||
// bug in Beego: this call will panic when file session store is empty
|
||||
// so we catch the panic
|
||||
username = ctx.Input.Session("username").(string)
|
||||
|
||||
if len(username) == 0 {
|
||||
if username == "" {
|
||||
username = getUsernameByClientIdSecret(ctx)
|
||||
}
|
||||
|
||||
@@ -70,22 +52,19 @@ func getSubject(ctx *context.Context) (string, string) {
|
||||
}
|
||||
|
||||
// username == "built-in/admin"
|
||||
tokens := strings.Split(username, "/")
|
||||
owner := tokens[0]
|
||||
name := tokens[1]
|
||||
return owner, name
|
||||
return util.GetOwnerAndNameFromId(username)
|
||||
}
|
||||
|
||||
func getObject(ctx *context.Context) (string, string) {
|
||||
method := ctx.Request.Method
|
||||
if method == http.MethodGet {
|
||||
query := ctx.Request.URL.RawQuery
|
||||
// query == "?id=built-in/admin"
|
||||
idParamValue := parseQuery(query, "id")
|
||||
if idParamValue == "" {
|
||||
id := ctx.Input.Query("id")
|
||||
if id == "" {
|
||||
return "", ""
|
||||
}
|
||||
return parseSlash(idParamValue)
|
||||
|
||||
return util.GetOwnerAndNameFromId(id)
|
||||
} else {
|
||||
body := ctx.Input.RequestBody
|
||||
|
||||
|
@@ -16,60 +16,23 @@ package routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/controllers"
|
||||
"github.com/casbin/casdoor/object"
|
||||
"github.com/casbin/casdoor/util"
|
||||
)
|
||||
|
||||
func getSessionUser(ctx *context.Context) string {
|
||||
user := ctx.Input.CruSession.Get("username")
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
func setSessionUser(ctx *context.Context, user string) {
|
||||
err := ctx.Input.CruSession.Set("username", user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// https://github.com/beego/beego/issues/3445#issuecomment-455411915
|
||||
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||
}
|
||||
|
||||
func returnRequest(ctx *context.Context, msg string) {
|
||||
w := ctx.ResponseWriter
|
||||
w.WriteHeader(200)
|
||||
resp := &controllers.Response{Status: "error", Msg: msg}
|
||||
_, err := w.Write([]byte(util.StructToJson(resp)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func AutoSigninFilter(ctx *context.Context) {
|
||||
//if getSessionUser(ctx) != "" {
|
||||
// return
|
||||
//}
|
||||
|
||||
query := ctx.Request.URL.RawQuery
|
||||
queryMap, err := url.ParseQuery(query)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// "/page?access_token=123"
|
||||
accessToken := queryMap.Get("accessToken")
|
||||
accessToken := ctx.Input.Query("accessToken")
|
||||
if accessToken != "" {
|
||||
claims, err := object.ParseJwtToken(accessToken)
|
||||
if err != nil {
|
||||
returnRequest(ctx, "Invalid JWT token")
|
||||
responseError(ctx, "invalid JWT token")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -78,14 +41,21 @@ func AutoSigninFilter(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// "/page?clientId=123&clientSecret=456"
|
||||
userId := getUsernameByClientIdSecret(ctx)
|
||||
if userId != "" {
|
||||
setSessionUser(ctx, userId)
|
||||
return
|
||||
}
|
||||
|
||||
// "/page?username=abc&password=123"
|
||||
userId := queryMap.Get("username")
|
||||
password := queryMap.Get("password")
|
||||
userId = ctx.Input.Query("username")
|
||||
password := ctx.Input.Query("password")
|
||||
if userId != "" && password != "" {
|
||||
owner, name := util.GetOwnerAndNameFromId(userId)
|
||||
_, msg := object.CheckUserPassword(owner, name, password)
|
||||
if msg != "" {
|
||||
returnRequest(ctx, msg)
|
||||
responseError(ctx, msg)
|
||||
return
|
||||
}
|
||||
|
||||
|
@@ -14,7 +14,12 @@
|
||||
|
||||
package routers
|
||||
|
||||
import "github.com/astaxie/beego/context"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/object"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Status string `json:"status"`
|
||||
@@ -42,3 +47,37 @@ func responseError(ctx *context.Context, error string, data ...interface{}) {
|
||||
func denyRequest(ctx *context.Context) {
|
||||
responseError(ctx, "Unauthorized operation")
|
||||
}
|
||||
|
||||
func getUsernameByClientIdSecret(ctx *context.Context) string {
|
||||
clientId := ctx.Input.Query("clientId")
|
||||
clientSecret := ctx.Input.Query("clientSecret")
|
||||
if clientId == "" || clientSecret == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
application := object.GetApplicationByClientId(clientId)
|
||||
if application == nil || application.ClientSecret != clientSecret {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("app/%s", application.Name)
|
||||
}
|
||||
|
||||
func getSessionUser(ctx *context.Context) string {
|
||||
user := ctx.Input.CruSession.Get("username")
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return user.(string)
|
||||
}
|
||||
|
||||
func setSessionUser(ctx *context.Context, user string) {
|
||||
err := ctx.Input.CruSession.Set("username", user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// https://github.com/beego/beego/issues/3445#issuecomment-455411915
|
||||
ctx.Input.CruSession.SessionRelease(ctx.ResponseWriter)
|
||||
}
|
||||
|
@@ -15,7 +15,7 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"fmt"
|
||||
|
||||
"github.com/astaxie/beego/context"
|
||||
"github.com/casbin/casdoor/object"
|
||||
@@ -39,31 +39,31 @@ func getUser(ctx *context.Context) (username string) {
|
||||
}
|
||||
|
||||
func getUserByClientIdSecret(ctx *context.Context) string {
|
||||
requestUri := ctx.Request.RequestURI
|
||||
clientId := parseQuery(requestUri, "clientId")
|
||||
clientSecret := parseQuery(requestUri, "clientSecret")
|
||||
if len(clientId) == 0 || len(clientSecret) == 0 {
|
||||
clientId := ctx.Input.Query("clientId")
|
||||
clientSecret := ctx.Input.Query("clientSecret")
|
||||
if clientId == "" || clientSecret == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
app := object.GetApplicationByClientId(clientId)
|
||||
if app == nil || app.ClientSecret != clientSecret {
|
||||
application := object.GetApplicationByClientId(clientId)
|
||||
if application == nil || application.ClientSecret != clientSecret {
|
||||
return ""
|
||||
}
|
||||
return app.Organization + "/" + app.Name
|
||||
|
||||
return fmt.Sprintf("%s/%s", application.Organization, application.Name)
|
||||
}
|
||||
|
||||
func RecordMessage(ctx *context.Context) {
|
||||
if ctx.Request.URL.Path != "/api/login" {
|
||||
user := getUser(ctx)
|
||||
userinfo := strings.Split(user, "/")
|
||||
if user == "" {
|
||||
userinfo = append(userinfo, "")
|
||||
}
|
||||
record := util.Records(ctx)
|
||||
record.Organization = userinfo[0]
|
||||
record.Username = userinfo[1]
|
||||
|
||||
object.AddRecord(record)
|
||||
if ctx.Request.URL.Path == "/api/login" {
|
||||
return
|
||||
}
|
||||
|
||||
record := util.Records(ctx)
|
||||
|
||||
userId := getUser(ctx)
|
||||
if userId != "" {
|
||||
record.Organization, record.Username = util.GetOwnerAndNameFromId(userId)
|
||||
}
|
||||
|
||||
object.AddRecord(record)
|
||||
}
|
||||
|
@@ -49,6 +49,7 @@ func initAPI() {
|
||||
|
||||
beego.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
|
||||
beego.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")
|
||||
beego.Router("/api/get-application-clientId", &controllers.ApiController{}, "Get:GetApplicationByClientId")
|
||||
beego.Router("/api/update-organization", &controllers.ApiController{}, "POST:UpdateOrganization")
|
||||
beego.Router("/api/add-organization", &controllers.ApiController{}, "POST:AddOrganization")
|
||||
beego.Router("/api/delete-organization", &controllers.ApiController{}, "POST:DeleteOrganization")
|
||||
@@ -108,4 +109,9 @@ func initAPI() {
|
||||
|
||||
beego.Router("/api/send-email", &controllers.ApiController{}, "POST:SendEmail")
|
||||
beego.Router("/api/send-sms", &controllers.ApiController{}, "POST:SendSms")
|
||||
|
||||
beego.Router("/api/paypal", &controllers.ApiController{}, "POST:PaypalPay")
|
||||
beego.Router("/api/success-pay", &controllers.ApiController{}, "GET:SuccessPay")
|
||||
beego.Router("/api/get-payments", &controllers.ApiController{}, "Get:GetPayments")
|
||||
beego.Router("/api/delete-payment", &controllers.ApiController{}, "POST:DeletePayment")
|
||||
}
|
||||
|
@@ -43,6 +43,11 @@ func GetOwnerAndNameFromId(id string) (string, string) {
|
||||
return tokens[0], tokens[1]
|
||||
}
|
||||
|
||||
func GetOwnerAndNameFromIdNoCheck(id string) (string, string) {
|
||||
tokens := strings.SplitN(id, "/", 2)
|
||||
return tokens[0], tokens[1]
|
||||
}
|
||||
|
||||
func GenerateId() string {
|
||||
return uuid.NewString()
|
||||
}
|
||||
|
166
web/src/App.js
166
web/src/App.js
@@ -50,6 +50,9 @@ import AuthCallback from "./auth/AuthCallback";
|
||||
import SelectLanguageBox from './SelectLanguageBox';
|
||||
import i18next from 'i18next';
|
||||
import PromptPage from "./auth/PromptPage";
|
||||
import PaySuccessCallback from "./PaySuccessCallback";
|
||||
import Pay from "./Pay";
|
||||
import PaymentListPage from "./PaymentListPage";
|
||||
|
||||
const { Header, Footer } = Layout;
|
||||
|
||||
@@ -72,7 +75,6 @@ class App extends Component {
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
Setting.setLanguage();
|
||||
this.updateMenuKey();
|
||||
this.getAccount();
|
||||
}
|
||||
@@ -113,6 +115,8 @@ class App extends Component {
|
||||
this.setState({ selectedMenuKey: '/login' });
|
||||
} else if (uri.includes('/result')) {
|
||||
this.setState({ selectedMenuKey: '/result' });
|
||||
}else if (uri.includes('/payment')) {
|
||||
this.setState({ selectedMenuKey: '/payment' });
|
||||
} else {
|
||||
this.setState({ selectedMenuKey: -1 });
|
||||
}
|
||||
@@ -139,6 +143,13 @@ class App extends Component {
|
||||
return location.toString().replace(location.search, "");
|
||||
}
|
||||
|
||||
setLanguage(account) {
|
||||
let language = account?.language;
|
||||
if (language !== "" && language !== i18next.language) {
|
||||
Setting.setLanguage(language);
|
||||
}
|
||||
}
|
||||
|
||||
getAccount() {
|
||||
let query = this.getAccessTokenParam();
|
||||
if (query === "") {
|
||||
@@ -153,6 +164,8 @@ class App extends Component {
|
||||
if (res.status === "ok") {
|
||||
account = res.data;
|
||||
account.organization = res.data2;
|
||||
|
||||
this.setLanguage(account);
|
||||
} else {
|
||||
if (res.msg !== "Please sign in first") {
|
||||
Setting.showMessage("error", `Failed to sign in: ${res.msg}`);
|
||||
@@ -194,9 +207,9 @@ class App extends Component {
|
||||
}
|
||||
|
||||
handleRightDropdownClick(e) {
|
||||
if (e.key === '201') {
|
||||
if (e.key === '/account') {
|
||||
this.props.history.push(`/account`);
|
||||
} else if (e.key === '202') {
|
||||
} else if (e.key === 'logout') {
|
||||
this.logout();
|
||||
}
|
||||
}
|
||||
@@ -220,11 +233,11 @@ class App extends Component {
|
||||
renderRightDropdown() {
|
||||
const menu = (
|
||||
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
|
||||
<Menu.Item key="201">
|
||||
<Menu.Item key="/account">
|
||||
<SettingOutlined />
|
||||
{i18next.t("account:My Account")}
|
||||
</Menu.Item>
|
||||
<Menu.Item key="202">
|
||||
<Menu.Item key="/logout">
|
||||
<LogoutOutlined />
|
||||
{i18next.t("account:Logout")}
|
||||
</Menu.Item>
|
||||
@@ -232,7 +245,7 @@ class App extends Component {
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown key="200" overlay={menu} className="rightDropDown">
|
||||
<Dropdown key="/rightDropDown" overlay={menu} className="rightDropDown">
|
||||
<div className="ant-dropdown-link" style={{float: 'right', cursor: 'pointer'}}>
|
||||
|
||||
|
||||
@@ -321,13 +334,17 @@ class App extends Component {
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/resources">
|
||||
<Link to="/resources">
|
||||
{i18next.t("general:Resources")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
}
|
||||
|
||||
res.push(
|
||||
<Menu.Item key="/resources">
|
||||
<Link to="/resources">
|
||||
{i18next.t("general:Resources")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
|
||||
if (Setting.isAdminUser(this.state.account)) {
|
||||
res.push(
|
||||
<Menu.Item key="/tokens">
|
||||
<Link to="/tokens">
|
||||
@@ -336,20 +353,29 @@ class App extends Component {
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/records">
|
||||
<Link to="/records">
|
||||
{i18next.t("general:Records")}
|
||||
<Menu.Item key="/records">
|
||||
<Link to="/records">
|
||||
{i18next.t("general:Records")}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
res.push(
|
||||
<Menu.Item key="/payment">
|
||||
<Link to="/payment">
|
||||
{"Payment"}
|
||||
</Link>
|
||||
</Menu.Item>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
res.push(
|
||||
<Menu.Item key="/swagger">
|
||||
<a target="_blank" rel="noreferrer" href={"/swagger"}>
|
||||
<a target="_blank" rel="noreferrer" href={Setting.isLocalhost ? `${Setting.ServerUrl}/swagger` : "/swagger"}>
|
||||
{i18next.t("general:Swagger")}
|
||||
</a>
|
||||
</Menu.Item>
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -402,6 +428,9 @@ class App extends Component {
|
||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)}/>
|
||||
<Route exact path="/payment" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
||||
<Route exact path="/pay" render={(props) => <Pay account={this.state.account} {...props}/>}/>
|
||||
<Route exact path="/pay/success" render={(props) => <PaySuccessCallback account={this.state.account} {...props}/>}/>
|
||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||
</Switch>
|
||||
@@ -414,31 +443,35 @@ class App extends Component {
|
||||
return (
|
||||
<div style={{display: 'flex', flex: 'auto',width:"100%",flexDirection: 'column'}}>
|
||||
<Layout style={{display: 'flex', alignItems: 'stretch'}}>
|
||||
<Header style={{ padding: '0', marginBottom: '3px'}}>
|
||||
{
|
||||
Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<Menu
|
||||
// theme="dark"
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{ lineHeight: '64px'}}
|
||||
>
|
||||
|
||||
{window.location.pathname.indexOf("/pay") != -1 && window.location.pathname.indexOf("/payment") == -1 ?
|
||||
null :
|
||||
(<Header style={{ padding: '0', marginBottom: '3px'}}>
|
||||
{
|
||||
this.renderMenu()
|
||||
Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<div style = {{float: 'right'}}>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox/>
|
||||
</div>
|
||||
</Menu>
|
||||
</Header>
|
||||
<Menu
|
||||
// theme="dark"
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{ lineHeight: '64px'}}
|
||||
>
|
||||
{
|
||||
this.renderMenu()
|
||||
}
|
||||
<div style = {{float: 'right'}}>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox/>
|
||||
</div>
|
||||
</Menu>
|
||||
</Header>)
|
||||
}
|
||||
<Layout style={{backgroundColor: "#f5f5f5", alignItems: 'stretch'}}>
|
||||
<Card className="content-warp-card">
|
||||
{
|
||||
@@ -452,31 +485,34 @@ class App extends Component {
|
||||
} else {
|
||||
return(
|
||||
<div>
|
||||
<Header style={{ padding: '0', marginBottom: '3px'}}>
|
||||
{
|
||||
Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" />
|
||||
</Link>
|
||||
)
|
||||
{window.location.pathname.indexOf("/pay") != -1 && window.location.pathname.indexOf("/payment") == -1 ?
|
||||
null :
|
||||
<Header style={{ padding: '0', marginBottom: '3px'}}>
|
||||
{
|
||||
Setting.isMobile() ? null : (
|
||||
<Link to={"/"}>
|
||||
<div className="logo" />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
<Menu
|
||||
// theme="dark"
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{ lineHeight: '64px' }}
|
||||
>
|
||||
{
|
||||
this.renderMenu()
|
||||
}
|
||||
<div style = {{float: 'right'}}>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox/>
|
||||
</div>
|
||||
</Menu>
|
||||
</Header>
|
||||
}
|
||||
<Menu
|
||||
// theme="dark"
|
||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||
style={{ lineHeight: '64px' }}
|
||||
>
|
||||
{
|
||||
this.renderMenu()
|
||||
}
|
||||
<div style = {{float: 'right'}}>
|
||||
{
|
||||
this.renderAccount()
|
||||
}
|
||||
<SelectLanguageBox/>
|
||||
</div>
|
||||
</Menu>
|
||||
</Header>
|
||||
{
|
||||
this.renderRouter()
|
||||
}
|
||||
|
@@ -99,7 +99,7 @@ class ApplicationEditPage extends React.Component {
|
||||
}
|
||||
this.setState({uploading: true});
|
||||
const fullFilePath = `termsOfUse/${this.state.application.owner}/${this.state.application.name}.html`;
|
||||
ResourceBackend.uploadResource(this.state.application.owner, "termsOfUse", this.state.application.name, fullFilePath, info.file)
|
||||
ResourceBackend.uploadResource(this.props.account.owner, this.props.account.name, "termsOfUse", "ApplicationEditPage", fullFilePath, info.file)
|
||||
.then(res => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("application:File uploaded successfully"));
|
||||
|
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
export const ShowGithubCorner = false
|
||||
export const ShowGithubCorner = false;
|
||||
export const GithubRepo = "https://github.com/casbin/casdoor";
|
||||
|
||||
export const GithubRepo = "https://github.com/casbin/casdoor"
|
||||
export const ForceLanguage = "en";
|
||||
export const DefaultLanguage = "en";
|
@@ -58,7 +58,7 @@ export const CropperDiv = (props) => {
|
||||
// Setting.showMessage("success", "uploading...");
|
||||
const extension = image.substring(image.indexOf('/') + 1, image.indexOf(';base64'));
|
||||
const fullFilePath = `avatar/${user.owner}/${user.name}.${extension}`;
|
||||
ResourceBackend.uploadResource("admin", "avatar", account.name, fullFilePath, blob)
|
||||
ResourceBackend.uploadResource(user.owner, user.name, "avatar", "CropperDiv", fullFilePath, blob)
|
||||
.then((res) => {
|
||||
if (res.status === "ok") {
|
||||
window.location.href = "/account";
|
||||
|
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import * as Conf from "./common/Conf"
|
||||
import * as Conf from "./Conf";
|
||||
import GithubCorner from "react-github-corner";
|
||||
|
||||
class CustomGithubCorner extends React.Component {
|
||||
|
176
web/src/Pay.js
Normal file
176
web/src/Pay.js
Normal file
@@ -0,0 +1,176 @@
|
||||
// Copyright 2021 The casbin 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, Form, Input,} from "antd";
|
||||
import {withRouter} from "react-router-dom";
|
||||
import * as PaymentBackend from "./backend/PaymentBackend"
|
||||
import * as ApplicationBackend from "./backend/ApplicationBackend"
|
||||
|
||||
class Pay extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
msg: null,
|
||||
classes: props,
|
||||
clientId: this.GetQueryString("clientId"),
|
||||
invoice: this.GetQueryString("invoice"),
|
||||
price: this.GetQueryString("price"),
|
||||
currency: this.GetQueryString("currency"),
|
||||
description: this.GetQueryString("description"),
|
||||
redirectUri: this.GetQueryString("redirectUri"),
|
||||
waiting: false,
|
||||
applicationName: "",
|
||||
wrongMsg: "",
|
||||
spinMsg: "正在生成订单..."
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getApplication();
|
||||
}
|
||||
|
||||
getApplication(){
|
||||
ApplicationBackend.getApplicationByClientId(this.state.clientId).then(res => {
|
||||
if(res == null){
|
||||
this.setState({
|
||||
wrongMsg: "Invalid clientID"
|
||||
})
|
||||
}else {
|
||||
console.log("res")
|
||||
console.log(res)
|
||||
this.setState({
|
||||
applicationName : res.displayName
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
GetQueryString(key){
|
||||
let reg = new RegExp("(^|&)"+ key +"=([^&]*)(&|$)");
|
||||
let r = window.location.search.substr(1).match(reg);
|
||||
if(r!=null)return unescape(r[2]); return null;
|
||||
}
|
||||
|
||||
getCurrencyString(currency){
|
||||
if(currency === "USD"){
|
||||
return "$"
|
||||
}else if (currency === "CNY"){
|
||||
return "¥"
|
||||
}else if (currency === "EUR"){
|
||||
return "€"
|
||||
}
|
||||
return "$"
|
||||
}
|
||||
|
||||
|
||||
pay(){
|
||||
let payItem = {
|
||||
invoice: this.state.invoice,
|
||||
price : this.state.price,
|
||||
currency : this.state.currency,
|
||||
description : this.state.description
|
||||
}
|
||||
this.setState({
|
||||
waiting: true
|
||||
})
|
||||
|
||||
PaymentBackend.PaypalPal(payItem, this.state.clientId, this.state.redirectUri).then(res => {
|
||||
console.log(res)
|
||||
if (res.indexOf("http") !== -1){
|
||||
this.setState({
|
||||
spinMsg: "前往支付页面"
|
||||
})
|
||||
window.location.replace(res);
|
||||
}
|
||||
else {
|
||||
this.setState({
|
||||
wrongMsg : res
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = [
|
||||
{
|
||||
title: 'Invoice',
|
||||
},
|
||||
{
|
||||
title: 'Amount',
|
||||
},
|
||||
{
|
||||
title: 'Description',
|
||||
}
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
(this.state.wrongMsg === "") ?
|
||||
(<div style={{textAlign: "center"}}>
|
||||
{
|
||||
(this.state.waiting) ? (
|
||||
<Spin size="large" tip={this.state.spinMsg} style={{paddingTop: "10%"}} />
|
||||
) : (
|
||||
<div style={{display: "inline"}}>
|
||||
<p style={{fontSize: 20}}>{`尊敬的用户,您正在通过 casdoor 向 ${this.state.applicationName} 进行付款,请确认`}</p>
|
||||
<Form
|
||||
size="large"
|
||||
name="pay"
|
||||
labelCol={{ span: 8 }}
|
||||
wrapperCol={{ span: 8 }}
|
||||
initialValues={{ remember: true }}
|
||||
autoComplete="off"
|
||||
>
|
||||
<Form.Item
|
||||
label="Invoice"
|
||||
>
|
||||
<Input style={{color: "black"}} value={this.state.invoice} bordered={false} disabled />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Account"
|
||||
>
|
||||
<Input style={{color: "black"}} disabled bordered={false} value={`${this.getCurrencyString(this.state.currency)} ${this.state.price}`}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Description"
|
||||
>
|
||||
<Input style={{color: "black"}} bordered={false} disabled value={`${this.state.description}`}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item wrapperCol={{ offset: 8, span: 8 }}>
|
||||
<Button size="large" type="primary" onClick={() => this.pay()}>
|
||||
确认付款
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>) :
|
||||
( <div style={{display: "inline"}}>
|
||||
<Result
|
||||
status="error"
|
||||
title="Pay Error"
|
||||
subTitle={this.state.wrongMsg}
|
||||
/>
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(Pay);
|
71
web/src/PaySuccessCallback.js
Normal file
71
web/src/PaySuccessCallback.js
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2021 The casbin 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 {withRouter} from "react-router-dom";
|
||||
import * as PaymentBackend from "./backend/PaymentBackend"
|
||||
|
||||
class PaySuccessCallback extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
msg: null,
|
||||
paymentId: this.GetQueryString("token"),
|
||||
payerId: this.GetQueryString("PayerID")
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/no-deprecated
|
||||
componentWillMount() {
|
||||
PaymentBackend.PaySuccess(this.state.paymentId, this.state.payerId).then(res => {
|
||||
window.location.href = res
|
||||
})
|
||||
}
|
||||
|
||||
GetQueryString(key){
|
||||
let reg = new RegExp("(^|&)"+ key +"=([^&]*)(&|$)");
|
||||
let r = window.location.search.substr(1).match(reg);
|
||||
if(r!=null)return unescape(r[2]); return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{textAlign: "center"}}>
|
||||
{
|
||||
(this.state.msg === null) ? (
|
||||
<Spin size="large" tip="支付完成,正在跳转..." style={{paddingTop: "10%"}} />
|
||||
) : (
|
||||
<div style={{display: "inline"}}>
|
||||
<Result
|
||||
status="error"
|
||||
title="Login Error"
|
||||
subTitle={this.state.msg}
|
||||
extra={[
|
||||
<Button type="primary" key="details">
|
||||
Details
|
||||
</Button>,
|
||||
<Button key="help">Help</Button>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(PaySuccessCallback);
|
208
web/src/PaymentListPage.js
Normal file
208
web/src/PaymentListPage.js
Normal file
@@ -0,0 +1,208 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from "react";
|
||||
import {Link} from "react-router-dom";
|
||||
import {Button, Popconfirm, Table} from 'antd';
|
||||
import moment from "moment";
|
||||
import * as Setting from "./Setting";
|
||||
import * as PaymentBackend from "./backend/PaymentBackend";
|
||||
import i18next from "i18next";
|
||||
|
||||
class PaymentListPage extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
classes: props,
|
||||
payments: [],
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
this.getPayments();
|
||||
}
|
||||
|
||||
getPayments() {
|
||||
PaymentBackend.getPayments().then(res => {
|
||||
this.setState({
|
||||
payments: res
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
deletePayment(i) {
|
||||
PaymentBackend.deletePayment(this.state.payments[i]).then(res => {
|
||||
Setting.showMessage("success", `Payment deleted successfully`);
|
||||
this.setState({
|
||||
payments: Setting.deleteRow(this.state.payments, i),
|
||||
});
|
||||
}).catch(error => {
|
||||
Setting.showMessage("error", `Payment failed to delete: ${error}`);
|
||||
});
|
||||
}
|
||||
|
||||
renderTable(payments) {
|
||||
const columns = [
|
||||
{
|
||||
title: "Id",
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
width: (Setting.isMobile()) ? "100px" : "170px",
|
||||
fixed: 'left',
|
||||
sorter: (a, b) => a.id.localeCompare(b.id),
|
||||
},
|
||||
{
|
||||
title: "invoice",
|
||||
dataIndex: 'invoice',
|
||||
key: 'invoice',
|
||||
width: '150px',
|
||||
sorter: (a, b) => a.invoice.localeCompare(b.invoice),
|
||||
},
|
||||
{
|
||||
title: "Application",
|
||||
dataIndex: 'application',
|
||||
key: 'application',
|
||||
width: '120px',
|
||||
sorter: (a, b) => a.application.localeCompare(b.application),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/applications/${text.substring(text.indexOf("/")+1)}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Amount",
|
||||
dataIndex: 'pay_item',
|
||||
key: 'pay_item',
|
||||
width: '150px',
|
||||
ellipsis: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
`amount: ${text.currency} ${text.price}`
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
width: '120px',
|
||||
sorter: (a, b) => a.status.localeCompare(b.status),
|
||||
},
|
||||
{
|
||||
title: "Payer",
|
||||
dataIndex: 'payer',
|
||||
key: 'payer',
|
||||
width: '120px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
text === null ? "" : (
|
||||
`${text?.name.surname} ${text?.name.given_name}`
|
||||
)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Payer Email",
|
||||
dataIndex: 'payer',
|
||||
key: 'payer',
|
||||
width: '160px',
|
||||
ellipsis: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
text === null ? "" : text.email_address
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Create_time",
|
||||
dataIndex: 'create_time',
|
||||
key: 'create_time',
|
||||
width: '150px',
|
||||
sorter: (a, b) => a.create_time.localeCompare(b.create_time),
|
||||
},
|
||||
{
|
||||
title: "update_time",
|
||||
dataIndex: 'update_time',
|
||||
key: 'update_time',
|
||||
width: '150px',
|
||||
sorter: (a, b) => a.update_time.localeCompare(b.update_time),
|
||||
},
|
||||
{
|
||||
title: "description",
|
||||
dataIndex: 'pay_item',
|
||||
key: 'pay_item',
|
||||
ellipsis: true,
|
||||
width: '150px',
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
text.description
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Callback",
|
||||
dataIndex: 'callback',
|
||||
key: 'callback',
|
||||
width: '100px',
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Action"),
|
||||
dataIndex: '',
|
||||
key: 'op',
|
||||
width: '80px',
|
||||
fixed: (Setting.isMobile()) ? "false" : "right",
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<div>
|
||||
<Popconfirm
|
||||
title={`Sure to delete this payment ?`}
|
||||
onConfirm={() => this.deletePayment(index)}
|
||||
>
|
||||
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: 'max-content'}} columns={columns} dataSource={payments} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
|
||||
title={() => (
|
||||
<div>
|
||||
{"Payments"}
|
||||
</div>
|
||||
)}
|
||||
loading={payments === null}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
this.renderTable(this.state.payments)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PaymentListPage;
|
@@ -77,6 +77,7 @@ class ProviderEditPage extends React.Component {
|
||||
{id: 'LinkedIn', name: 'LinkedIn'},
|
||||
{id: 'WeCom', name: 'WeCom'},
|
||||
{id: 'Lark', name: 'Lark'},
|
||||
{id: 'GitLab', name: 'GitLab'},
|
||||
]
|
||||
);
|
||||
} else if (provider.category === "Email") {
|
||||
@@ -101,6 +102,12 @@ class ProviderEditPage extends React.Component {
|
||||
{id: 'Aliyun OSS', name: 'Aliyun OSS'},
|
||||
]
|
||||
);
|
||||
} else if (provider.category === "Payment") {
|
||||
return (
|
||||
[
|
||||
{id: 'Paypal', name: 'PayPay'}
|
||||
]
|
||||
)
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
@@ -200,6 +207,8 @@ class ProviderEditPage extends React.Component {
|
||||
} else if (value === "Storage") {
|
||||
this.updateProviderField('type', 'Local File System');
|
||||
this.updateProviderField('domain', Setting.getFullServerUrl());
|
||||
} else if (value === "Pay") {
|
||||
this.updateProviderField('type', 'Paypal')
|
||||
}
|
||||
})}>
|
||||
{
|
||||
@@ -208,6 +217,7 @@ class ProviderEditPage extends React.Component {
|
||||
{id: 'Email', name: 'Email'},
|
||||
{id: 'SMS', name: 'SMS'},
|
||||
{id: 'Storage', name: 'Storage'},
|
||||
{id: 'Payment', name: 'Payment'}
|
||||
].map((providerCategory, index) => <Option key={index} value={providerCategory.id}>{providerCategory.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
@@ -230,6 +240,22 @@ class ProviderEditPage extends React.Component {
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
{this.state.provider.type === "WeCom" ? (
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={2}>
|
||||
{Setting.getLabel(i18next.t("provider:Method"), i18next.t("provider:Method - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Select virtual={false} style={{width: '100%'}} value={this.state.provider.method} onChange={value => {
|
||||
this.updateProviderField('method', value);
|
||||
}}>
|
||||
{
|
||||
[{name: "Normal"}, {name: "Silent"}].map((method, index) => <Option key={index} value={method.name}>{method.name}</Option>)
|
||||
}
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
) : null}
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{this.getClientIdLabel()}
|
||||
|
@@ -51,6 +51,7 @@ class ProviderListPage extends React.Component {
|
||||
displayName: `New Provider - ${this.state.providers.length}`,
|
||||
category: "OAuth",
|
||||
type: "GitHub",
|
||||
method: "Normal",
|
||||
clientId: "",
|
||||
clientSecret: "",
|
||||
enableSignUp: true,
|
||||
|
@@ -151,6 +151,7 @@ class ProviderTable extends React.Component {
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
// {
|
||||
// title: i18next.t("provider:alertType"),
|
||||
// dataIndex: 'alertType',
|
||||
|
@@ -37,7 +37,7 @@ class ResourceListPage extends React.Component {
|
||||
}
|
||||
|
||||
getResources() {
|
||||
ResourceBackend.getResources("admin")
|
||||
ResourceBackend.getResources(this.props.account.owner, this.props.account.name)
|
||||
.then((res) => {
|
||||
this.setState({
|
||||
resources: res,
|
||||
@@ -63,7 +63,7 @@ class ResourceListPage extends React.Component {
|
||||
this.setState({uploading: true});
|
||||
const filename = info.fileList[0].name;
|
||||
const fullFilePath = `resource/${this.props.account.owner}/${this.props.account.name}/${filename}`;
|
||||
ResourceBackend.uploadResource("admin", "custom", this.props.account.name, fullFilePath, info.file)
|
||||
ResourceBackend.uploadResource(this.props.account.owner, this.props.account.name, "custom", "ResourceListPage", fullFilePath, info.file)
|
||||
.then(res => {
|
||||
if (res.status === "ok") {
|
||||
Setting.showMessage("success", i18next.t("application:File uploaded successfully"));
|
||||
@@ -104,6 +104,41 @@ class ResourceListPage extends React.Component {
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("resource:Application"),
|
||||
dataIndex: 'application',
|
||||
key: 'application',
|
||||
width: '80px',
|
||||
sorter: (a, b) => a.application.localeCompare(b.application),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/applications/${text}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("resource:User"),
|
||||
dataIndex: 'user',
|
||||
key: 'user',
|
||||
width: '80px',
|
||||
sorter: (a, b) => a.user.localeCompare(b.user),
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
<Link to={`/users/${record.owner}/${record.user}`}>
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: i18next.t("resource:Parent"),
|
||||
dataIndex: 'parent',
|
||||
key: 'parent',
|
||||
width: '80px',
|
||||
sorter: (a, b) => a.parent.localeCompare(b.parent),
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Name"),
|
||||
dataIndex: 'name',
|
||||
@@ -115,7 +150,7 @@ class ResourceListPage extends React.Component {
|
||||
title: i18next.t("general:Created time"),
|
||||
dataIndex: 'createdTime',
|
||||
key: 'createdTime',
|
||||
width: '160px',
|
||||
width: '150px',
|
||||
sorter: (a, b) => a.createdTime.localeCompare(b.createdTime),
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
@@ -129,31 +164,31 @@ class ResourceListPage extends React.Component {
|
||||
sorter: (a, b) => a.tag.localeCompare(b.tag),
|
||||
},
|
||||
{
|
||||
title: i18next.t("resource:Parent"),
|
||||
dataIndex: 'parent',
|
||||
key: 'parent',
|
||||
width: '80px',
|
||||
sorter: (a, b) => a.parent.localeCompare(b.parent),
|
||||
title: i18next.t("resource:File name"),
|
||||
dataIndex: 'fileName',
|
||||
key: 'fileName',
|
||||
width: '120px',
|
||||
sorter: (a, b) => a.fileName.localeCompare(b.fileName),
|
||||
},
|
||||
{
|
||||
title: i18next.t("resource:File type"),
|
||||
title: i18next.t("resource:Type"),
|
||||
dataIndex: 'fileType',
|
||||
key: 'fileType',
|
||||
width: '120px',
|
||||
width: '80px',
|
||||
sorter: (a, b) => a.fileType.localeCompare(b.fileType),
|
||||
},
|
||||
{
|
||||
title: i18next.t("resource:File format"),
|
||||
title: i18next.t("resource:Format"),
|
||||
dataIndex: 'fileFormat',
|
||||
key: 'fileFormat',
|
||||
width: '130px',
|
||||
width: '80px',
|
||||
sorter: (a, b) => a.fileFormat.localeCompare(b.fileFormat),
|
||||
},
|
||||
{
|
||||
title: i18next.t("resource:File size"),
|
||||
dataIndex: 'fileSize',
|
||||
key: 'fileSize',
|
||||
width: '120px',
|
||||
width: '100px',
|
||||
sorter: (a, b) => a.fileSize - b.fileSize,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFriendlyFileSize(text);
|
||||
|
@@ -30,13 +30,11 @@ class SelectLanguageBox extends React.Component {
|
||||
};
|
||||
}
|
||||
|
||||
onClick(e) {
|
||||
Setting.changeLanguage(e.key);
|
||||
};
|
||||
|
||||
render() {
|
||||
const menu = (
|
||||
<Menu onClick={this.onClick.bind(this)}>
|
||||
<Menu onClick={(e) => {
|
||||
Setting.changeLanguage(e.key);
|
||||
}}>
|
||||
<Menu.Item key="en" icon={<IconFont type="icon-en" />}>English</Menu.Item>
|
||||
<Menu.Item key="zh" icon={<IconFont type="icon-zh" />}>简体中文</Menu.Item>
|
||||
<Menu.Item key="fr" icon={<IconFont type="icon-fr" />}>Français</Menu.Item>
|
||||
|
@@ -21,6 +21,7 @@ import i18next from "i18next";
|
||||
import copy from "copy-to-clipboard";
|
||||
import {authConfig} from "./auth/Auth";
|
||||
import {Helmet} from "react-helmet";
|
||||
import moment from "moment";
|
||||
|
||||
export let ServerUrl = "";
|
||||
|
||||
@@ -43,7 +44,7 @@ export function initServerUrl() {
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalhost() {
|
||||
export function isLocalhost() {
|
||||
const hostname = window.location.hostname;
|
||||
return hostname === "localhost";
|
||||
}
|
||||
@@ -308,20 +309,43 @@ export function getAvatarColor(s) {
|
||||
return colorList[random % 4];
|
||||
}
|
||||
|
||||
export function setLanguage() {
|
||||
let language = localStorage.getItem('language');
|
||||
if (language === undefined) {
|
||||
language = "en"
|
||||
}
|
||||
i18next.changeLanguage(language)
|
||||
export function setLanguage(language) {
|
||||
localStorage.setItem("language", language);
|
||||
changeMomentLanguage(language);
|
||||
i18next.changeLanguage(language);
|
||||
}
|
||||
|
||||
export function changeLanguage(language) {
|
||||
localStorage.setItem("language", language)
|
||||
i18next.changeLanguage(language)
|
||||
localStorage.setItem("language", language);
|
||||
changeMomentLanguage(language);
|
||||
i18next.changeLanguage(language);
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
export function changeMomentLanguage(language) {
|
||||
return;
|
||||
if (language === "zh") {
|
||||
moment.locale("zh", {
|
||||
relativeTime: {
|
||||
future: "%s内",
|
||||
past: "%s前",
|
||||
s: "几秒",
|
||||
ss: "%d秒",
|
||||
m: "1分钟",
|
||||
mm: "%d分钟",
|
||||
h: "1小时",
|
||||
hh: "%d小时",
|
||||
d: "1天",
|
||||
dd: "%d天",
|
||||
M: "1个月",
|
||||
MM: "%d个月",
|
||||
y: "1年",
|
||||
yy: "%d年",
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function getClickable(text) {
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/anchor-is-valid
|
||||
|
@@ -236,11 +236,51 @@ class UserEditPage extends React.Component {
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Location"), i18next.t("user:Location - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.location} onChange={e => {
|
||||
this.updateUserField('location', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
{
|
||||
(this.state.application === null || this.state.user === null) ? null : (
|
||||
<AffiliationSelect labelSpan={(Setting.isMobile()) ? 22 : 2} application={this.state.application} user={this.state.user} onUpdateUserField={(key, value) => { return this.updateUserField(key, value)}} />
|
||||
)
|
||||
}
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Title"), i18next.t("user:Title - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.title} onChange={e => {
|
||||
this.updateUserField('title', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Homepage"), i18next.t("user:Homepage - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.homepage} onChange={e => {
|
||||
this.updateUserField('homepage', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Bio"), i18next.t("user:Bio - Tooltip"))} :
|
||||
</Col>
|
||||
<Col span={22} >
|
||||
<Input value={this.state.user.bio} onChange={e => {
|
||||
this.updateUserField('bio', e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
<Row style={{marginTop: '20px'}} >
|
||||
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Tag"), i18next.t("user:Tag - Tooltip"))} :
|
||||
|
32
web/src/auth/GitLabLoginButton.js
Normal file
32
web/src/auth/GitLabLoginButton.js
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2021 The casbin 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/gitlab.svg`} alt="Sign in with GitLab" style={{width: 24, height: 24}} />;
|
||||
}
|
||||
|
||||
const config = {
|
||||
text: "Sign in with GitLab",
|
||||
icon: Icon,
|
||||
iconFormat: name => `fa fa-${name}`,
|
||||
style: {background: "rgb(255,255,255)", color: "#000000"},
|
||||
activeStyle: {background: "rgb(100,150,250)"},
|
||||
};
|
||||
|
||||
const GitLabLoginButton = createButton(config);
|
||||
|
||||
export default GitLabLoginButton;
|
@@ -32,6 +32,7 @@ import i18next from "i18next";
|
||||
import LinkedInLoginButton from "./LinkedInLoginButton";
|
||||
import WeComLoginButton from "./WeComLoginButton";
|
||||
import LarkLoginButton from "./LarkLoginButton";
|
||||
import GitLabLoginButton from "./GitLabLoginButton";
|
||||
|
||||
class LoginPage extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -170,6 +171,8 @@ class LoginPage extends React.Component {
|
||||
return <WeComLoginButton text={text} align={"center"} />
|
||||
} else if (type === "Lark") {
|
||||
return <LarkLoginButton text={text} align={"center"} />
|
||||
} else if (type === "GitLab") {
|
||||
return <GitLabLoginButton text={text} align={"center"} />
|
||||
}
|
||||
|
||||
return text;
|
||||
|
@@ -43,7 +43,7 @@ const WeiboAuthScope = "email";
|
||||
const WeiboAuthUri = "https://api.weibo.com/oauth2/authorize";
|
||||
const WeiboAuthLogo = `${StaticBaseUrl}/img/social_weibo.png`;
|
||||
|
||||
const GiteeAuthScope = "user_info,emails";
|
||||
const GiteeAuthScope = "user_info%20emails";
|
||||
const GiteeAuthUri = "https://gitee.com/oauth/authorize";
|
||||
const GiteeAuthLogo = `${StaticBaseUrl}/img/social_gitee.png`;
|
||||
|
||||
@@ -51,14 +51,19 @@ const LinkedInAuthScope = "r_liteprofile%20r_emailaddress";
|
||||
const LinkedInAuthUri = "https://www.linkedin.com/oauth/v2/authorization";
|
||||
const LinkedInAuthLogo = `${StaticBaseUrl}/img/social_linkedin.png`;
|
||||
|
||||
// const WeComAuthScope = "";
|
||||
const WeComSilentAuthScope = "snsapi_userinfo";
|
||||
const WeComAuthUri = "https://open.work.weixin.qq.com/wwopen/sso/3rd_qrConnect";
|
||||
const WeComSilentAuthUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";
|
||||
const WeComAuthLogo = `${StaticBaseUrl}/img/social_wecom.png`;
|
||||
|
||||
// const WeComAuthScope = "";
|
||||
// const LarkAuthScope = "";
|
||||
const LarkAuthUri = "https://open.feishu.cn/open-apis/authen/v1/index";
|
||||
const LarkAuthLogo = `${StaticBaseUrl}/img/social_lark.png`;
|
||||
|
||||
const GitLabAuthScope = "read_user+profile";
|
||||
const GitLabAuthUri = "https://gitlab.com/oauth/authorize";
|
||||
const GitLabAuthLogo = `${StaticBaseUrl}/img/social_gitlab.png`;
|
||||
|
||||
export function getAuthLogo(provider) {
|
||||
if (provider.type === "Google") {
|
||||
return GoogleAuthLogo;
|
||||
@@ -82,6 +87,8 @@ export function getAuthLogo(provider) {
|
||||
return WeComAuthLogo;
|
||||
} else if (provider.type === "Lark") {
|
||||
return LarkAuthLogo;
|
||||
} else if (provider.type === "GitLab") {
|
||||
return GitLabAuthLogo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,10 +116,18 @@ export function getAuthUrl(application, provider, method) {
|
||||
} else if (provider.type === "Gitee") {
|
||||
return `${GiteeAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${GiteeAuthScope}&response_type=code&state=${state}`;
|
||||
} else if (provider.type === "LinkedIn") {
|
||||
return `${LinkedInAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${LinkedInAuthScope}&response_type=code&state=${state}`
|
||||
return `${LinkedInAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&scope=${LinkedInAuthScope}&response_type=code&state=${state}`;
|
||||
} else if (provider.type === "WeCom") {
|
||||
return `${WeComAuthUri}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&usertype=member`
|
||||
if (provider.method === "Silent") {
|
||||
return `${WeComSilentAuthUrl}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&scope=${WeComSilentAuthScope}&response_type=code#wechat_redirect`;
|
||||
} else if (provider.method === "Normal") {
|
||||
return `${WeComAuthUri}?appid=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&usertype=member`;
|
||||
} else {
|
||||
return `https://error:not-supported-provider-method:${provider.method}`;
|
||||
}
|
||||
} else if (provider.type === "Lark") {
|
||||
return `${LarkAuthUri}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}`
|
||||
return `${LarkAuthUri}?app_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}`;
|
||||
} else if (provider.type === "GitLab") {
|
||||
return `${GitLabAuthUri}?client_id=${provider.clientId}&redirect_uri=${redirectUri}&state=${state}&response_type=code&scope=${GitLabAuthScope}`;
|
||||
}
|
||||
}
|
||||
|
@@ -62,3 +62,10 @@ export function deleteApplication(application) {
|
||||
body: JSON.stringify(newApplication),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getApplicationByClientId(clientId){
|
||||
return fetch(`${Setting.ServerUrl}/api/get-application-clientId?clientId=${clientId}`, {
|
||||
method: "GET",
|
||||
credentials: "include"
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
47
web/src/backend/PaymentBackend.js
Normal file
47
web/src/backend/PaymentBackend.js
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2021 The casbin Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
export function PaySuccess(paymentId, payerID){
|
||||
return fetch(`${Setting.ServerUrl}/api/success-pay?paymentId=${paymentId}&payerId=${payerID}`, {
|
||||
method: "GET",
|
||||
credentials: "include",
|
||||
}).then(res => res.json())
|
||||
}
|
||||
|
||||
export function PaypalPal(payItem,clientId, redirectUri) {
|
||||
return fetch(`http://localhost:8000/api/paypal?clientId=${clientId}&redirectUri=${redirectUri}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body: JSON.stringify(payItem),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function getPayments(){
|
||||
return fetch(`${Setting.ServerUrl}/api/get-payments?`, {
|
||||
method: "GET",
|
||||
credentials: "include"
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function deletePayment(payment){
|
||||
let newPayment = Setting.deepCopy(payment);
|
||||
return fetch(`${Setting.ServerUrl}/api/delete-payment`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
body: JSON.stringify(newPayment),
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
@@ -14,8 +14,8 @@
|
||||
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
export function getResources(owner) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-resources?owner=${owner}`, {
|
||||
export function getResources(owner, user) {
|
||||
return fetch(`${Setting.ServerUrl}/api/get-resources?owner=${owner}&user=${user}`, {
|
||||
method: "GET",
|
||||
credentials: "include"
|
||||
}).then(res => res.json());
|
||||
@@ -55,10 +55,11 @@ export function deleteResource(resource, provider="") {
|
||||
}).then(res => res.json());
|
||||
}
|
||||
|
||||
export function uploadResource(owner, tag, parent, fullFilePath, file, provider="") {
|
||||
export function uploadResource(owner, user, tag, parent, fullFilePath, file, provider="") {
|
||||
const application = "app-built-in";
|
||||
let formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return fetch(`${Setting.ServerUrl}/api/upload-resource?owner=${owner}&tag=${tag}&parent=${parent}&fullFilePath=${encodeURIComponent(fullFilePath)}&provider=${provider}`, {
|
||||
return fetch(`${Setting.ServerUrl}/api/upload-resource?owner=${owner}&user=${user}&application=${application}&tag=${tag}&parent=${parent}&fullFilePath=${encodeURIComponent(fullFilePath)}&provider=${provider}`, {
|
||||
body: formData,
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
|
@@ -12,38 +12,85 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import i18n from 'i18next'
|
||||
import zh from './locales/zh/data.json'
|
||||
import en from './locales/en/data.json'
|
||||
import fr from './locales/fr/data.json'
|
||||
import de from './locales/de/data.json'
|
||||
import ko from './locales/ko/data.json'
|
||||
import ru from './locales/ru/data.json'
|
||||
import ja from './locales/ja/data.json'
|
||||
import i18n from "i18next";
|
||||
import zh from "./locales/zh/data.json";
|
||||
import en from "./locales/en/data.json";
|
||||
import fr from "./locales/fr/data.json";
|
||||
import de from "./locales/de/data.json";
|
||||
import ko from "./locales/ko/data.json";
|
||||
import ru from "./locales/ru/data.json";
|
||||
import ja from "./locales/ja/data.json";
|
||||
import * as Conf from "./Conf";
|
||||
import * as Setting from "./Setting";
|
||||
|
||||
const resources = {
|
||||
en: en,
|
||||
zh: zh,
|
||||
fr: fr,
|
||||
ja: ja,
|
||||
de: de,
|
||||
ko: ko,
|
||||
ru: ru,
|
||||
ja: ja,
|
||||
};
|
||||
|
||||
i18n
|
||||
.init({
|
||||
lng: "en",
|
||||
function initLanguage() {
|
||||
let language = localStorage.getItem("language");
|
||||
if (language === undefined || language == null) {
|
||||
if (Conf.ForceLanguage !== "") {
|
||||
language = Conf.ForceLanguage;
|
||||
} else {
|
||||
let userLanguage;
|
||||
userLanguage = navigator.language;
|
||||
switch (userLanguage) {
|
||||
case "zh-CN":
|
||||
language = "zh";
|
||||
break;
|
||||
case "zh":
|
||||
language = "zh";
|
||||
break;
|
||||
case "en":
|
||||
language = "en";
|
||||
break;
|
||||
case "en-US":
|
||||
language = "en";
|
||||
break;
|
||||
case "fr":
|
||||
language = "fr";
|
||||
break;
|
||||
case "de":
|
||||
language = "de";
|
||||
break;
|
||||
case "ko":
|
||||
language = "ko";
|
||||
break;
|
||||
case "ru":
|
||||
language = "ru";
|
||||
break;
|
||||
case "ja":
|
||||
language = "ja";
|
||||
break;
|
||||
default:
|
||||
language = Conf.DefaultLanguage;
|
||||
}
|
||||
}
|
||||
}
|
||||
Setting.changeMomentLanguage(language);
|
||||
|
||||
resources: resources,
|
||||
return language;
|
||||
}
|
||||
|
||||
keySeparator: false,
|
||||
i18n.init({
|
||||
lng: initLanguage(),
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
},
|
||||
|
||||
saveMissing: true,
|
||||
})
|
||||
resources: resources,
|
||||
|
||||
export default i18n;
|
||||
keySeparator: false,
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
//debug: true,
|
||||
saveMissing: true,
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
@@ -79,7 +79,12 @@
|
||||
"Signin URL": "Signin URL",
|
||||
"Signin URL - Tooltip": "sign in url",
|
||||
"ID - Tooltip": "random string",
|
||||
"Favicon - Tooltip": "Application icon"
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"Uploading": "Uploading",
|
||||
"Start Upload": "Start Upload",
|
||||
"Upload success": "Upload success",
|
||||
"Back Home": "Back Home",
|
||||
"Sorry, the page you visited does not exist": "Sorry, the page you visited does not exist"
|
||||
},
|
||||
"signup": {
|
||||
"Username": "Username",
|
||||
@@ -159,6 +164,8 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Login behaviors, QR code or silent authorization",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client secret": "Client secret",
|
||||
@@ -186,7 +193,13 @@
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Region": "Region",
|
||||
"Region - Tooltip": "Storage region"
|
||||
"Region - Tooltip": "Storage region",
|
||||
"Access key": "Access key",
|
||||
"Access key - Tooltip": "Access key - Tooltip",
|
||||
"Secret access key": "Secret access key",
|
||||
"Secret access key - Tooltip": "Secret access key - Tooltip",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip"
|
||||
},
|
||||
"user": {
|
||||
"Edit User": "Edit User",
|
||||
|
@@ -171,6 +171,8 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Login behaviors, QR code or silent authorization",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client secret": "Client secret",
|
||||
|
@@ -79,7 +79,12 @@
|
||||
"Signin URL": "Signin URL",
|
||||
"Signin URL - Tooltip": "sign in url",
|
||||
"ID - Tooltip": "random string",
|
||||
"Favicon - Tooltip": "Application icon"
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"Uploading": "Uploading",
|
||||
"Start Upload": "Start Upload",
|
||||
"Upload success": "Upload success",
|
||||
"Back Home": "Back Home",
|
||||
"Sorry, the page you visited does not exist": "Sorry, the page you visited does not exist"
|
||||
},
|
||||
"signup": {
|
||||
"Username": "Username",
|
||||
@@ -159,6 +164,8 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Login behaviors, QR code or silent authorization",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client secret": "Client secret",
|
||||
@@ -186,7 +193,13 @@
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Region": "Region",
|
||||
"Region - Tooltip": "Storage region"
|
||||
"Region - Tooltip": "Storage region",
|
||||
"Access key": "Access key",
|
||||
"Access key - Tooltip": "Access key - Tooltip",
|
||||
"Secret access key": "Secret access key",
|
||||
"Secret access key - Tooltip": "Secret access key - Tooltip",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip"
|
||||
},
|
||||
"user": {
|
||||
"Edit User": "Edit User",
|
||||
|
@@ -79,7 +79,12 @@
|
||||
"Signin URL": "Signin URL",
|
||||
"Signin URL - Tooltip": "sign in url",
|
||||
"ID - Tooltip": "random string",
|
||||
"Favicon - Tooltip": "Application icon"
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"Uploading": "Uploading",
|
||||
"Start Upload": "Start Upload",
|
||||
"Upload success": "Upload success",
|
||||
"Back Home": "Back Home",
|
||||
"Sorry, the page you visited does not exist": "Sorry, the page you visited does not exist"
|
||||
},
|
||||
"signup": {
|
||||
"Username": "Username",
|
||||
@@ -159,6 +164,8 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Login behaviors, QR code or silent authorization",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client secret": "Client secret",
|
||||
@@ -186,7 +193,13 @@
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Region": "Region",
|
||||
"Region - Tooltip": "Storage region"
|
||||
"Region - Tooltip": "Storage region",
|
||||
"Access key": "Access key",
|
||||
"Access key - Tooltip": "Access key - Tooltip",
|
||||
"Secret access key": "Secret access key",
|
||||
"Secret access key - Tooltip": "Secret access key - Tooltip",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip"
|
||||
},
|
||||
"user": {
|
||||
"Edit User": "Edit User",
|
||||
|
@@ -79,7 +79,12 @@
|
||||
"Signin URL": "Signin URL",
|
||||
"Signin URL - Tooltip": "sign in url",
|
||||
"ID - Tooltip": "random string",
|
||||
"Favicon - Tooltip": "Application icon"
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"Uploading": "Uploading",
|
||||
"Start Upload": "Start Upload",
|
||||
"Upload success": "Upload success",
|
||||
"Back Home": "Back Home",
|
||||
"Sorry, the page you visited does not exist": "Sorry, the page you visited does not exist"
|
||||
},
|
||||
"signup": {
|
||||
"Username": "Username",
|
||||
@@ -159,6 +164,8 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Login behaviors, QR code or silent authorization",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client secret": "Client secret",
|
||||
@@ -186,7 +193,13 @@
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Region": "Region",
|
||||
"Region - Tooltip": "Storage region"
|
||||
"Region - Tooltip": "Storage region",
|
||||
"Access key": "Access key",
|
||||
"Access key - Tooltip": "Access key - Tooltip",
|
||||
"Secret access key": "Secret access key",
|
||||
"Secret access key - Tooltip": "Secret access key - Tooltip",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip"
|
||||
},
|
||||
"user": {
|
||||
"Edit User": "Edit User",
|
||||
|
@@ -79,7 +79,12 @@
|
||||
"Signin URL": "Signin URL",
|
||||
"Signin URL - Tooltip": "sign in url",
|
||||
"ID - Tooltip": "random string",
|
||||
"Favicon - Tooltip": "Application icon"
|
||||
"Favicon - Tooltip": "Application icon",
|
||||
"Uploading": "Uploading",
|
||||
"Start Upload": "Start Upload",
|
||||
"Upload success": "Upload success",
|
||||
"Back Home": "Back Home",
|
||||
"Sorry, the page you visited does not exist": "Sorry, the page you visited does not exist"
|
||||
},
|
||||
"signup": {
|
||||
"Username": "Username",
|
||||
@@ -159,6 +164,8 @@
|
||||
"Category - Tooltip": "Unique string-style identifier",
|
||||
"Type": "Type",
|
||||
"Type - Tooltip": "Unique string-style identifier",
|
||||
"Method": "Method",
|
||||
"Method - Tooltip": "Login behaviors, QR code or silent authorization",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "Unique string-style identifier",
|
||||
"Client secret": "Client secret",
|
||||
@@ -186,7 +193,13 @@
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Region": "Region",
|
||||
"Region - Tooltip": "Storage region"
|
||||
"Region - Tooltip": "Storage region",
|
||||
"Access key": "Access key",
|
||||
"Access key - Tooltip": "Access key - Tooltip",
|
||||
"Secret access key": "Secret access key",
|
||||
"Secret access key - Tooltip": "Secret access key - Tooltip",
|
||||
"SMS account": "SMS account",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip"
|
||||
},
|
||||
"user": {
|
||||
"Edit User": "Edit User",
|
||||
|
@@ -12,11 +12,11 @@
|
||||
"User": "用户",
|
||||
"Applications": "应用",
|
||||
"Application": "应用",
|
||||
"Records": "Records",
|
||||
"Client ip": "Client ip",
|
||||
"Timestamp": "Timestamp",
|
||||
"Records": "日志",
|
||||
"Client ip": "客户端地址",
|
||||
"Timestamp": "时间戳",
|
||||
"Username": "用户名",
|
||||
"Request uri": "Request uri",
|
||||
"Request uri": "请求地址",
|
||||
"LDAPs": "LDAPs",
|
||||
"Save": "保存",
|
||||
"Add": "添加",
|
||||
@@ -79,7 +79,12 @@
|
||||
"Signin URL": "登录URL",
|
||||
"Signin URL - Tooltip": "用户的登录地址",
|
||||
"ID - Tooltip": "唯一的随机字符串",
|
||||
"Favicon - Tooltip": "网站的图标"
|
||||
"Favicon - Tooltip": "网站的图标",
|
||||
"Uploading": "上传中",
|
||||
"Start Upload": "开始上传",
|
||||
"Upload success": "上传成功",
|
||||
"Back Home": "返回到首页",
|
||||
"Sorry, the page you visited does not exist": "很抱歉,你访问的页面不存在"
|
||||
},
|
||||
"signup": {
|
||||
"Username": "用户名",
|
||||
@@ -87,7 +92,7 @@
|
||||
"Please input your personal name!": "请输入您的姓名!",
|
||||
"Please input your address!": "请输入您的地址!",
|
||||
"Please input your affiliation!": "请输入您所在的工作单位!",
|
||||
"Please select your country/region!": "Please select your country/region!",
|
||||
"Please select your country/region!": "请选择您的国家/地区",
|
||||
"The input is not valid Email!": "您输入的电子邮箱格式错误!",
|
||||
"Please input your Email!": "请输入您的电子邮箱!",
|
||||
"Confirm": "确认密码",
|
||||
@@ -159,6 +164,8 @@
|
||||
"Category - Tooltip": "唯一的、字符串式的ID",
|
||||
"Type": "类型",
|
||||
"Type - Tooltip": "唯一的、字符串式的ID",
|
||||
"Method": "方式",
|
||||
"Method - Tooltip": "登录行为,二维码或者静默授权登录",
|
||||
"Client ID": "Client ID",
|
||||
"Client ID - Tooltip": "唯一的、字符串式的ID",
|
||||
"Client secret": "Client secret",
|
||||
@@ -179,14 +186,20 @@
|
||||
"Provider URL": "提供商URL",
|
||||
"Provider URL - Tooltip": "唯一的、字符串式的ID",
|
||||
"Edit Provider": "修改提供商",
|
||||
"Endpoint": "Endpoint",
|
||||
"Endpoint": "节点",
|
||||
"Endpoint - Tooltip": "Storage bucket endpoint",
|
||||
"Bucket": "Bucket",
|
||||
"Bucket - Tooltip": "Storage bucket name",
|
||||
"Domain": "Domain",
|
||||
"Domain - Tooltip": "Storage endpoint custom domain",
|
||||
"Region": "Region",
|
||||
"Region - Tooltip": "Storage region"
|
||||
"Bucket": "存储桶",
|
||||
"Bucket - Tooltip": "存储桶名称",
|
||||
"Domain": "域名",
|
||||
"Domain - Tooltip": "存储节点自定义域名",
|
||||
"Region": "地区",
|
||||
"Region - Tooltip": "存储区域",
|
||||
"Access key": "Access key",
|
||||
"Access key - Tooltip": "Access key - Tooltip",
|
||||
"Secret access key": "Secret access key",
|
||||
"Secret access key - Tooltip": "Secret access key - Tooltip",
|
||||
"SMS account": "SMS账号",
|
||||
"SMS account - Tooltip": "SMS account - Tooltip"
|
||||
},
|
||||
"user": {
|
||||
"Edit User": "修改用户",
|
||||
@@ -199,8 +212,8 @@
|
||||
"Address - Tooltip": "唯一的、字符串式的ID",
|
||||
"Affiliation": "工作单位",
|
||||
"Affiliation - Tooltip": "唯一的、字符串式的ID",
|
||||
"Country/Region": "Country/Region",
|
||||
"Country/Region - Tooltip": "Country/Region",
|
||||
"Country/Region": "国家/地区",
|
||||
"Country/Region - Tooltip": "国家/地区",
|
||||
"Modify affiliation": "修改工作单位",
|
||||
"Tag": "标签",
|
||||
"Tag - Tooltip": "唯一的、字符串式的ID",
|
||||
|
Reference in New Issue
Block a user