mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-11 08:07:45 +08:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a38747d90e | ||
![]() |
da70682cd1 | ||
![]() |
4a3bd84f84 | ||
![]() |
7f2869cecb | ||
![]() |
cef2ab213b | ||
![]() |
cc979c310e | ||
![]() |
13d73732ce | ||
![]() |
5686fe5d22 | ||
![]() |
d8cb82f67a | ||
![]() |
cad2e1bcc3 | ||
![]() |
52cc2e4fa7 | ||
![]() |
8077a2ccba | ||
![]() |
4cb8e4a514 | ||
![]() |
2f48d45773 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
cache-dependency-path: ./web/yarn.lock
|
cache-dependency-path: ./web/yarn.lock
|
||||||
- run: yarn install && CI=false yarn run build
|
- run: yarn install && CI=false yarn run build
|
||||||
@@ -101,7 +101,7 @@ jobs:
|
|||||||
working-directory: ./
|
working-directory: ./
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
cache-dependency-path: ./web/yarn.lock
|
cache-dependency-path: ./web/yarn.lock
|
||||||
- run: yarn install
|
- run: yarn install
|
||||||
@@ -138,7 +138,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
|
|
||||||
- name: Fetch Previous version
|
- name: Fetch Previous version
|
||||||
id: get-previous-tag
|
id: get-previous-tag
|
||||||
|
@@ -16,6 +16,7 @@ package controllers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/beego/beego/utils/pagination"
|
"github.com/beego/beego/utils/pagination"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
@@ -163,11 +164,17 @@ func (c *ApiController) GetPolicies() {
|
|||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if adapter == nil {
|
||||||
|
c.ResponseError(fmt.Sprintf(c.T("the adapter: %s is not found"), adapterId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = adapter.InitAdapter()
|
err = adapter.InitAdapter()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
c.ResponseError(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk()
|
c.ResponseOk()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -46,10 +46,10 @@ func (c *ApiController) GetSystemInfo() {
|
|||||||
// @Success 200 {object} util.VersionInfo The Response object
|
// @Success 200 {object} util.VersionInfo The Response object
|
||||||
// @router /get-version-info [get]
|
// @router /get-version-info [get]
|
||||||
func (c *ApiController) GetVersionInfo() {
|
func (c *ApiController) GetVersionInfo() {
|
||||||
|
errInfo := ""
|
||||||
versionInfo, err := util.GetVersionInfo()
|
versionInfo, err := util.GetVersionInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
errInfo = "Git error: " + err.Error()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if versionInfo.Version != "" {
|
if versionInfo.Version != "" {
|
||||||
@@ -59,9 +59,11 @@ func (c *ApiController) GetVersionInfo() {
|
|||||||
|
|
||||||
versionInfo, err = util.GetVersionInfoFromFile()
|
versionInfo, err = util.GetVersionInfoFromFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.ResponseError(err.Error())
|
errInfo = errInfo + ", File error: " + err.Error()
|
||||||
|
c.ResponseError(errInfo)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.ResponseOk(versionInfo)
|
c.ResponseOk(versionInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -333,6 +333,35 @@ func (c *ApiController) IntrospectToken() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if application.TokenFormat == "JWT-Standard" {
|
||||||
|
jwtToken, err := object.ParseStandardJwtTokenByApplication(tokenValue, application)
|
||||||
|
if err != nil || jwtToken.Valid() != nil {
|
||||||
|
// and token revoked case. but we not implement
|
||||||
|
// TODO: 2022-03-03 add token revoked check, when we implemented the Token Revocation(rfc7009) Specs.
|
||||||
|
// refs: https://tools.ietf.org/html/rfc7009
|
||||||
|
c.Data["json"] = &object.IntrospectionResponse{Active: false}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data["json"] = &object.IntrospectionResponse{
|
||||||
|
Active: true,
|
||||||
|
Scope: jwtToken.Scope,
|
||||||
|
ClientId: clientId,
|
||||||
|
Username: token.User,
|
||||||
|
TokenType: token.TokenType,
|
||||||
|
Exp: jwtToken.ExpiresAt.Unix(),
|
||||||
|
Iat: jwtToken.IssuedAt.Unix(),
|
||||||
|
Nbf: jwtToken.NotBefore.Unix(),
|
||||||
|
Sub: jwtToken.Subject,
|
||||||
|
Aud: jwtToken.Audience,
|
||||||
|
Iss: jwtToken.Issuer,
|
||||||
|
Jti: jwtToken.ID,
|
||||||
|
}
|
||||||
|
c.ServeJSON()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
jwtToken, err := object.ParseJwtTokenByApplication(tokenValue, application)
|
||||||
if err != nil || jwtToken.Valid() != nil {
|
if err != nil || jwtToken.Valid() != nil {
|
||||||
// and token revoked case. but we not implement
|
// and token revoked case. but we not implement
|
||||||
|
14
idp/lark.go
14
idp/lark.go
@@ -22,6 +22,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nyaruka/phonenumbers"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -199,12 +200,25 @@ func (idp *LarkIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var phoneNumber string
|
||||||
|
var countryCode string
|
||||||
|
if len(larkUserInfo.Data.Mobile) != 0 {
|
||||||
|
phoneNumberParsed, err := phonenumbers.Parse(larkUserInfo.Data.Mobile, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
countryCode = phonenumbers.GetRegionCodeForNumber(phoneNumberParsed)
|
||||||
|
phoneNumber = fmt.Sprintf("%d", phoneNumberParsed.GetNationalNumber())
|
||||||
|
}
|
||||||
|
|
||||||
userInfo := UserInfo{
|
userInfo := UserInfo{
|
||||||
Id: larkUserInfo.Data.OpenId,
|
Id: larkUserInfo.Data.OpenId,
|
||||||
DisplayName: larkUserInfo.Data.EnName,
|
DisplayName: larkUserInfo.Data.EnName,
|
||||||
Username: larkUserInfo.Data.Name,
|
Username: larkUserInfo.Data.Name,
|
||||||
Email: larkUserInfo.Data.Email,
|
Email: larkUserInfo.Data.Email,
|
||||||
AvatarUrl: larkUserInfo.Data.AvatarUrl,
|
AvatarUrl: larkUserInfo.Data.AvatarUrl,
|
||||||
|
Phone: phoneNumber,
|
||||||
|
CountryCode: countryCode,
|
||||||
}
|
}
|
||||||
return &userInfo, nil
|
return &userInfo, nil
|
||||||
}
|
}
|
||||||
|
@@ -109,6 +109,7 @@ func initBuiltInOrganization() bool {
|
|||||||
EnableSoftDeletion: false,
|
EnableSoftDeletion: false,
|
||||||
IsProfilePublic: false,
|
IsProfilePublic: false,
|
||||||
UseEmailAsUsername: false,
|
UseEmailAsUsername: false,
|
||||||
|
EnableTour: true,
|
||||||
}
|
}
|
||||||
_, err = AddOrganization(organization)
|
_, err = AddOrganization(organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -73,6 +73,7 @@ type Organization struct {
|
|||||||
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
EnableSoftDeletion bool `json:"enableSoftDeletion"`
|
||||||
IsProfilePublic bool `json:"isProfilePublic"`
|
IsProfilePublic bool `json:"isProfilePublic"`
|
||||||
UseEmailAsUsername bool `json:"useEmailAsUsername"`
|
UseEmailAsUsername bool `json:"useEmailAsUsername"`
|
||||||
|
EnableTour bool `json:"enableTour"`
|
||||||
|
|
||||||
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
|
||||||
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
|
||||||
|
@@ -201,7 +201,7 @@ func notifyPayment(body []byte, owner string, paymentName string) (*Payment, *pp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if payment.IsRecharge {
|
if payment.IsRecharge {
|
||||||
err = updateUserBalance(payment.Owner, payment.User, payment.Price)
|
err = UpdateUserBalance(payment.Owner, payment.User, payment.Price)
|
||||||
return payment, notifyResult, err
|
return payment, notifyResult, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,6 +222,19 @@ func NotifyPayment(body []byte, owner string, paymentName string) (*Payment, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction, err := GetTransaction(payment.GetId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if transaction != nil {
|
||||||
|
transaction.State = payment.State
|
||||||
|
_, err = UpdateTransaction(transaction.GetId(), transaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return payment, nil
|
return payment, nil
|
||||||
|
@@ -181,15 +181,15 @@ func UpdatePermission(id string, permission *Permission) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
|
// if oldPermission.Adapter != "" && oldPermission.Adapter != permission.Adapter {
|
||||||
isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
|
// isEmpty, _ := ormer.Engine.IsTableEmpty(oldPermission.Adapter)
|
||||||
if isEmpty {
|
// if isEmpty {
|
||||||
err = ormer.Engine.DropTables(oldPermission.Adapter)
|
// err = ormer.Engine.DropTables(oldPermission.Adapter)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return false, err
|
// return false, err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
err = addGroupingPolicies(permission)
|
err = addGroupingPolicies(permission)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -312,15 +312,15 @@ func DeletePermission(permission *Permission) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if permission.Adapter != "" && permission.Adapter != "permission_rule" {
|
// if permission.Adapter != "" && permission.Adapter != "permission_rule" {
|
||||||
isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
|
// isEmpty, _ := ormer.Engine.IsTableEmpty(permission.Adapter)
|
||||||
if isEmpty {
|
// if isEmpty {
|
||||||
err = ormer.Engine.DropTables(permission.Adapter)
|
// err = ormer.Engine.DropTables(permission.Adapter)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return false, err
|
// return false, err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return affected, nil
|
return affected, nil
|
||||||
|
@@ -227,13 +227,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
NotifyUrl: notifyUrl,
|
NotifyUrl: notifyUrl,
|
||||||
PaymentEnv: paymentEnv,
|
PaymentEnv: paymentEnv,
|
||||||
}
|
}
|
||||||
|
|
||||||
// custom process for WeChat & WeChat Pay
|
// custom process for WeChat & WeChat Pay
|
||||||
if provider.Type == "WeChat Pay" {
|
if provider.Type == "WeChat Pay" {
|
||||||
payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2))
|
payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
} else if provider.Type == "Balance" {
|
||||||
|
payReq.PayerId = user.GetId()
|
||||||
}
|
}
|
||||||
|
|
||||||
payResp, err := pProvider.Pay(payReq)
|
payResp, err := pProvider.Pay(payReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
@@ -264,12 +268,46 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
OutOrderId: payResp.OrderId,
|
OutOrderId: payResp.OrderId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction := &Transaction{
|
||||||
|
Owner: payment.Owner,
|
||||||
|
Name: payment.Name,
|
||||||
|
DisplayName: payment.DisplayName,
|
||||||
|
Provider: provider.Name,
|
||||||
|
Category: provider.Category,
|
||||||
|
Type: provider.Type,
|
||||||
|
|
||||||
|
ProductName: product.Name,
|
||||||
|
ProductDisplayName: product.DisplayName,
|
||||||
|
Detail: product.Detail,
|
||||||
|
Tag: product.Tag,
|
||||||
|
Currency: product.Currency,
|
||||||
|
Amount: payment.Price,
|
||||||
|
ReturnUrl: payment.ReturnUrl,
|
||||||
|
|
||||||
|
User: payment.User,
|
||||||
|
Application: owner,
|
||||||
|
Payment: payment.GetId(),
|
||||||
|
|
||||||
|
State: pp.PaymentStateCreated,
|
||||||
|
}
|
||||||
|
|
||||||
if provider.Type == "Dummy" {
|
if provider.Type == "Dummy" {
|
||||||
payment.State = pp.PaymentStatePaid
|
payment.State = pp.PaymentStatePaid
|
||||||
err = updateUserBalance(user.Owner, user.Name, payment.Price)
|
err = UpdateUserBalance(user.Owner, user.Name, payment.Price)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
} else if provider.Type == "Balance" {
|
||||||
|
if product.Price > user.Balance {
|
||||||
|
return nil, nil, fmt.Errorf("insufficient user balance")
|
||||||
|
}
|
||||||
|
transaction.Amount = -transaction.Amount
|
||||||
|
err = UpdateUserBalance(user.Owner, user.Name, -product.Price)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
payment.State = pp.PaymentStatePaid
|
||||||
|
transaction.State = pp.PaymentStatePaid
|
||||||
}
|
}
|
||||||
|
|
||||||
affected, err := AddPayment(payment)
|
affected, err := AddPayment(payment)
|
||||||
@@ -280,6 +318,17 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
|
|||||||
if !affected {
|
if !affected {
|
||||||
return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if product.IsRecharge || provider.Type == "Balance" {
|
||||||
|
affected, err = AddTransaction(transaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !affected {
|
||||||
|
return nil, nil, fmt.Errorf("failed to add transaction: %s", util.StructToJson(payment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return payment, payResp.AttachInfo, nil
|
return payment, payResp.AttachInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -309,6 +309,12 @@ func GetPaymentProvider(p *Provider) (pp.PaymentProvider, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return pp, nil
|
return pp, nil
|
||||||
|
} else if typ == "Balance" {
|
||||||
|
pp, err := pp.NewBalancePaymentProvider()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pp, nil
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
return nil, fmt.Errorf("the payment provider type: %s is not supported", p.Type)
|
||||||
}
|
}
|
||||||
|
@@ -139,6 +139,15 @@ type ClaimsShort struct {
|
|||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OIDCAddress struct {
|
||||||
|
Formatted string `json:"formatted"`
|
||||||
|
StreetAddress string `json:"street_address"`
|
||||||
|
Locality string `json:"locality"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
PostalCode string `json:"postal_code"`
|
||||||
|
Country string `json:"country"`
|
||||||
|
}
|
||||||
|
|
||||||
type ClaimsWithoutThirdIdp struct {
|
type ClaimsWithoutThirdIdp struct {
|
||||||
*UserWithoutThirdIdp
|
*UserWithoutThirdIdp
|
||||||
TokenType string `json:"tokenType,omitempty"`
|
TokenType string `json:"tokenType,omitempty"`
|
||||||
@@ -386,6 +395,13 @@ func generateJwtToken(application *Application, user *User, nonce string, scope
|
|||||||
refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime)
|
refreshClaims["exp"] = jwt.NewNumericDate(refreshExpireTime)
|
||||||
refreshClaims["TokenType"] = "refresh-token"
|
refreshClaims["TokenType"] = "refresh-token"
|
||||||
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, refreshClaims)
|
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, refreshClaims)
|
||||||
|
} else if application.TokenFormat == "JWT-Standard" {
|
||||||
|
claimsStandard := getStandardClaims(claims)
|
||||||
|
|
||||||
|
token = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsStandard)
|
||||||
|
claimsStandard.ExpiresAt = jwt.NewNumericDate(refreshExpireTime)
|
||||||
|
claimsStandard.TokenType = "refresh-token"
|
||||||
|
refreshToken = jwt.NewWithClaims(jwt.SigningMethodRS256, claimsStandard)
|
||||||
} else {
|
} else {
|
||||||
return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat)
|
return "", "", "", fmt.Errorf("unknown application TokenFormat: %s", application.TokenFormat)
|
||||||
}
|
}
|
||||||
|
@@ -309,12 +309,22 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = ParseJwtToken(refreshToken, cert)
|
if application.TokenFormat == "JWT-Standard" {
|
||||||
if err != nil {
|
_, err = ParseStandardJwtToken(refreshToken, cert)
|
||||||
return &TokenError{
|
if err != nil {
|
||||||
Error: InvalidGrant,
|
return &TokenError{
|
||||||
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
Error: InvalidGrant,
|
||||||
}, nil
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_, err = ParseJwtToken(refreshToken, cert)
|
||||||
|
if err != nil {
|
||||||
|
return &TokenError{
|
||||||
|
Error: InvalidGrant,
|
||||||
|
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a new token
|
// generate a new token
|
||||||
|
106
object/token_standard_jwt.go
Normal file
106
object/token_standard_jwt.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClaimsStandard struct {
|
||||||
|
*UserShort
|
||||||
|
Gender string `json:"gender,omitempty"`
|
||||||
|
TokenType string `json:"tokenType,omitempty"`
|
||||||
|
Nonce string `json:"nonce,omitempty"`
|
||||||
|
Scope string `json:"scope,omitempty"`
|
||||||
|
Address OIDCAddress `json:"address,omitempty"`
|
||||||
|
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStreetAddress(user *User) string {
|
||||||
|
var addrs string
|
||||||
|
for _, addr := range user.Address {
|
||||||
|
addrs += addr + "\n"
|
||||||
|
}
|
||||||
|
return addrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStandardClaims(claims Claims) ClaimsStandard {
|
||||||
|
res := ClaimsStandard{
|
||||||
|
UserShort: getShortUser(claims.User),
|
||||||
|
TokenType: claims.TokenType,
|
||||||
|
Nonce: claims.Nonce,
|
||||||
|
Scope: claims.Scope,
|
||||||
|
RegisteredClaims: claims.RegisteredClaims,
|
||||||
|
}
|
||||||
|
|
||||||
|
var scopes []string
|
||||||
|
|
||||||
|
if strings.Contains(claims.Scope, ",") {
|
||||||
|
scopes = strings.Split(claims.Scope, ",")
|
||||||
|
} else {
|
||||||
|
scopes = strings.Split(claims.Scope, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scope := range scopes {
|
||||||
|
if scope == "address" {
|
||||||
|
res.Address = OIDCAddress{StreetAddress: getStreetAddress(claims.User)}
|
||||||
|
} else if scope == "profile" {
|
||||||
|
res.Gender = claims.User.Gender
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseStandardJwtToken(token string, cert *Cert) (*ClaimsStandard, error) {
|
||||||
|
t, err := jwt.ParseWithClaims(token, &ClaimsStandard{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||||
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if cert.Certificate == "" {
|
||||||
|
return nil, fmt.Errorf("the certificate field should not be empty for the cert: %v", cert)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RSA certificate
|
||||||
|
certificate, err := jwt.ParseRSAPublicKeyFromPEM([]byte(cert.Certificate))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return certificate, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if t != nil {
|
||||||
|
if claims, ok := t.Claims.(*ClaimsStandard); ok && t.Valid {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseStandardJwtTokenByApplication(token string, application *Application) (*ClaimsStandard, error) {
|
||||||
|
cert, err := getCertByApplication(application)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseStandardJwtToken(token, cert)
|
||||||
|
}
|
@@ -17,6 +17,7 @@ package object
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/pp"
|
||||||
"github.com/casdoor/casdoor/util"
|
"github.com/casdoor/casdoor/util"
|
||||||
"github.com/xorm-io/core"
|
"github.com/xorm-io/core"
|
||||||
)
|
)
|
||||||
@@ -43,7 +44,7 @@ type Transaction struct {
|
|||||||
Application string `xorm:"varchar(100)" json:"application"`
|
Application string `xorm:"varchar(100)" json:"application"`
|
||||||
Payment string `xorm:"varchar(100)" json:"payment"`
|
Payment string `xorm:"varchar(100)" json:"payment"`
|
||||||
|
|
||||||
State string `xorm:"varchar(100)" json:"state"`
|
State pp.PaymentState `xorm:"varchar(100)" json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTransactionCount(owner, field, value string) (int64, error) {
|
func GetTransactionCount(owner, field, value string) (int64, error) {
|
||||||
|
@@ -1158,7 +1158,7 @@ func GenerateIdForNewUser(application *Application) (string, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUserBalance(owner string, name string, balance float64) error {
|
func UpdateUserBalance(owner string, name string, balance float64) error {
|
||||||
user, err := getUser(owner, name)
|
user, err := getUser(owner, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
50
pp/balance.go
Normal file
50
pp/balance.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package pp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BalancePaymentProvider struct{}
|
||||||
|
|
||||||
|
func NewBalancePaymentProvider() (*BalancePaymentProvider, error) {
|
||||||
|
pp := &BalancePaymentProvider{}
|
||||||
|
return pp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *BalancePaymentProvider) Pay(r *PayReq) (*PayResp, error) {
|
||||||
|
owner, _ := util.GetOwnerAndNameFromId(r.PayerId)
|
||||||
|
return &PayResp{
|
||||||
|
PayUrl: r.ReturnUrl,
|
||||||
|
OrderId: fmt.Sprintf("%s/%s", owner, r.PaymentName),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *BalancePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {
|
||||||
|
return &NotifyResult{
|
||||||
|
PaymentStatus: PaymentStatePaid,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *BalancePaymentProvider) GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pp *BalancePaymentProvider) GetResponseError(err error) string {
|
||||||
|
return ""
|
||||||
|
}
|
@@ -35,20 +35,13 @@ type Object struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getUsername(ctx *context.Context) (username string) {
|
func getUsername(ctx *context.Context) (username string) {
|
||||||
defer func() {
|
username, ok := ctx.Input.Session("username").(string)
|
||||||
if r := recover(); r != nil {
|
if !ok || username == "" {
|
||||||
username, _ = getUsernameByClientIdSecret(ctx)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
username = ctx.Input.Session("username").(string)
|
|
||||||
|
|
||||||
if username == "" {
|
|
||||||
username, _ = getUsernameByClientIdSecret(ctx)
|
username, _ = getUsernameByClientIdSecret(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = getUsernameByKeys(ctx)
|
username, _ = getUsernameByKeys(ctx)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -91,17 +91,22 @@ func getUsernameByClientIdSecret(ctx *context.Context) (string, error) {
|
|||||||
return fmt.Sprintf("app/%s", application.Name), nil
|
return fmt.Sprintf("app/%s", application.Name), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUsernameByKeys(ctx *context.Context) string {
|
func getUsernameByKeys(ctx *context.Context) (string, error) {
|
||||||
accessKey, accessSecret := getKeys(ctx)
|
accessKey, accessSecret := getKeys(ctx)
|
||||||
user, err := object.GetUserByAccessKey(accessKey)
|
user, err := object.GetUserByAccessKey(accessKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil && accessSecret == user.AccessSecret {
|
if user == nil {
|
||||||
return user.GetId()
|
return "", fmt.Errorf("user not found for access key: %s", accessKey)
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
|
if accessSecret != user.AccessSecret {
|
||||||
|
return "", fmt.Errorf("incorrect access secret for user: %s", user.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return user.GetId(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSessionUser(ctx *context.Context) string {
|
func getSessionUser(ctx *context.Context) string {
|
||||||
|
@@ -16,6 +16,7 @@ import React, {Component, Suspense, lazy} from "react";
|
|||||||
import "./App.less";
|
import "./App.less";
|
||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
|
import {setOrgIsTourVisible, setTourLogo} from "./TourConfig";
|
||||||
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
import {StyleProvider, legacyLogicalPropertiesTransformer} from "@ant-design/cssinjs";
|
||||||
import {GithubOutlined, InfoCircleFilled, ShareAltOutlined} from "@ant-design/icons";
|
import {GithubOutlined, InfoCircleFilled, ShareAltOutlined} from "@ant-design/icons";
|
||||||
import {Alert, Button, ConfigProvider, Drawer, FloatButton, Layout, Result, Tooltip} from "antd";
|
import {Alert, Button, ConfigProvider, Drawer, FloatButton, Layout, Result, Tooltip} from "antd";
|
||||||
@@ -247,6 +248,8 @@ class App extends Component {
|
|||||||
|
|
||||||
this.setLanguage(account);
|
this.setLanguage(account);
|
||||||
this.setTheme(Setting.getThemeData(account.organization), Conf.InitThemeAlgorithm);
|
this.setTheme(Setting.getThemeData(account.organization), Conf.InitThemeAlgorithm);
|
||||||
|
setTourLogo(account.organization.logo);
|
||||||
|
setOrgIsTourVisible(account.organization.enableTour);
|
||||||
} else {
|
} else {
|
||||||
if (res.data !== "Please login first") {
|
if (res.data !== "Please login first") {
|
||||||
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
|
||||||
|
@@ -384,7 +384,7 @@ class ApplicationEditPage extends React.Component {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={22} >
|
<Col span={22} >
|
||||||
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField("tokenFormat", value);})}
|
<Select virtual={false} style={{width: "100%"}} value={this.state.application.tokenFormat} onChange={(value => {this.updateApplicationField("tokenFormat", value);})}
|
||||||
options={["JWT", "JWT-Empty", "JWT-Custom"].map((item) => Setting.getOption(item, item))}
|
options={["JWT", "JWT-Empty", "JWT-Custom", "JWT-Standard"].map((item) => Setting.getOption(item, item))}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
@@ -446,6 +446,16 @@ class OrganizationEditPage extends React.Component {
|
|||||||
}} />
|
}} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row style={{marginTop: "20px"}} >
|
||||||
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
|
||||||
|
{Setting.getLabel(i18next.t("general:Enable tour"), i18next.t("general:Enable tour - Tooltip"))} :
|
||||||
|
</Col>
|
||||||
|
<Col span={1} >
|
||||||
|
<Switch checked={this.state.organization.enableTour} onChange={checked => {
|
||||||
|
this.updateOrganizationField("enableTour", checked);
|
||||||
|
}} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<Row style={{marginTop: "20px"}} >
|
<Row style={{marginTop: "20px"}} >
|
||||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||||
{Setting.getLabel(i18next.t("organization:Account items"), i18next.t("organization:Account items - Tooltip"))} :
|
{Setting.getLabel(i18next.t("organization:Account items"), i18next.t("organization:Account items - Tooltip"))} :
|
||||||
|
@@ -44,6 +44,7 @@ class OrganizationListPage extends BaseListPage {
|
|||||||
defaultPassword: "",
|
defaultPassword: "",
|
||||||
enableSoftDeletion: false,
|
enableSoftDeletion: false,
|
||||||
isProfilePublic: true,
|
isProfilePublic: true,
|
||||||
|
enableTour: true,
|
||||||
accountItems: [
|
accountItems: [
|
||||||
{name: "Organization", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
{name: "Organization", visible: true, viewRule: "Public", modifyRule: "Admin"},
|
||||||
{name: "ID", visible: true, viewRule: "Public", modifyRule: "Immutable"},
|
{name: "ID", visible: true, viewRule: "Public", modifyRule: "Immutable"},
|
||||||
|
@@ -122,7 +122,7 @@ class PaymentResultPage extends React.Component {
|
|||||||
payment: payment,
|
payment: payment,
|
||||||
});
|
});
|
||||||
if (payment.state === "Created") {
|
if (payment.state === "Created") {
|
||||||
if (["PayPal", "Stripe", "Alipay", "WeChat Pay"].includes(payment.type)) {
|
if (["PayPal", "Stripe", "Alipay", "WeChat Pay", "Balance"].includes(payment.type)) {
|
||||||
this.setState({
|
this.setState({
|
||||||
timeout: setTimeout(async() => {
|
timeout: setTimeout(async() => {
|
||||||
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);
|
||||||
|
@@ -487,6 +487,7 @@ class PermissionEditPage extends React.Component {
|
|||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
Setting.showMessage("success", i18next.t("general:Successfully saved"));
|
||||||
this.setState({
|
this.setState({
|
||||||
|
organizationName: this.state.permission.owner,
|
||||||
permissionName: this.state.permission.name,
|
permissionName: this.state.permission.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -725,7 +725,7 @@ class ProviderEditPage extends React.Component {
|
|||||||
(this.state.provider.category === "Web3") ||
|
(this.state.provider.category === "Web3") ||
|
||||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ||
|
(this.state.provider.category === "Storage" && this.state.provider.type === "Local File System") ||
|
||||||
(this.state.provider.category === "SMS" && this.state.provider.type === "Custom HTTP SMS") ||
|
(this.state.provider.category === "SMS" && this.state.provider.type === "Custom HTTP SMS") ||
|
||||||
(this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP")) ? null : (
|
(this.state.provider.category === "Notification" && (this.state.provider.type === "Google Chat" || this.state.provider.type === "Custom HTTP") || this.state.provider.type === "Balance") ? null : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{
|
{
|
||||||
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||
|
(this.state.provider.category === "Storage" && this.state.provider.type === "Google Cloud Storage") ||
|
||||||
|
@@ -247,6 +247,10 @@ export const OtherProviderInfo = {
|
|||||||
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
|
logo: `${StaticBaseUrl}/img/payment_paypal.png`,
|
||||||
url: "",
|
url: "",
|
||||||
},
|
},
|
||||||
|
"Balance": {
|
||||||
|
logo: `${StaticBaseUrl}/img/payment_balance.svg`,
|
||||||
|
url: "",
|
||||||
|
},
|
||||||
"Alipay": {
|
"Alipay": {
|
||||||
logo: `${StaticBaseUrl}/img/payment_alipay.png`,
|
logo: `${StaticBaseUrl}/img/payment_alipay.png`,
|
||||||
url: "https://www.alipay.com/",
|
url: "https://www.alipay.com/",
|
||||||
@@ -1067,6 +1071,7 @@ export function getProviderTypeOptions(category) {
|
|||||||
} else if (category === "Payment") {
|
} else if (category === "Payment") {
|
||||||
return ([
|
return ([
|
||||||
{id: "Dummy", name: "Dummy"},
|
{id: "Dummy", name: "Dummy"},
|
||||||
|
{id: "Balance", name: "Balance"},
|
||||||
{id: "Alipay", name: "Alipay"},
|
{id: "Alipay", name: "Alipay"},
|
||||||
{id: "WeChat Pay", name: "WeChat Pay"},
|
{id: "WeChat Pay", name: "WeChat Pay"},
|
||||||
{id: "PayPal", name: "PayPal"},
|
{id: "PayPal", name: "PayPal"},
|
||||||
|
@@ -203,13 +203,24 @@ export function getNextUrl(pathName = window.location.pathname) {
|
|||||||
return TourUrlList[TourUrlList.indexOf(pathName.replace("/", "")) + 1] || "";
|
return TourUrlList[TourUrlList.indexOf(pathName.replace("/", "")) + 1] || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let orgIsTourVisible = true;
|
||||||
|
|
||||||
|
export function setOrgIsTourVisible(visible) {
|
||||||
|
orgIsTourVisible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
export function setIsTourVisible(visible) {
|
export function setIsTourVisible(visible) {
|
||||||
localStorage.setItem("isTourVisible", visible);
|
localStorage.setItem("isTourVisible", visible);
|
||||||
window.dispatchEvent(new Event("storageTourChanged"));
|
}
|
||||||
|
|
||||||
|
export function setTourLogo(tourLogoSrc) {
|
||||||
|
if (tourLogoSrc !== "") {
|
||||||
|
TourObj["home"][0]["cover"] = (<img alt="casdoor.png" src={tourLogoSrc} />);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTourVisible() {
|
export function getTourVisible() {
|
||||||
return localStorage.getItem("isTourVisible") !== "false";
|
return localStorage.getItem("isTourVisible") !== "false" && orgIsTourVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNextButtonChild(nextPathName) {
|
export function getNextButtonChild(nextPathName) {
|
||||||
|
Reference in New Issue
Block a user