Compare commits

...

15 Commits

Author SHA1 Message Date
23f4684e1d feat: make MFA works for CAS login (#2506)
* feat: make MFA works for CAS login

* fix: Reduced code redundancy

* fix: Modified the format of the code.

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

* Update LoginPage.js

* Update LoginPage.js

* Update LoginPage.js

* Update MfaAuthVerifyForm.js

---------

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

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

* feat: add log

* feat: update sign

* feat: process wechat pay result

* feat: process wechat pay result

* feat: save wechat openid for different app

* feat: save wechat openid for different app

* feat: add SetUserOAuthProperties for signup

* feat: fix openid for wechat

* feat: get user extra property in buyproduct

* feat: remove log

* feat: remove log

* feat: gofumpt code

* feat: change lr->crlf

* feat: change crlf->lf

* feat: improve code
2023-11-11 17:16:57 +08:00
d090e9c860 Improve downloadImage() 2023-11-10 08:35:21 +08:00
8ebb158765 feat: improve README 2023-11-09 21:52:52 +08:00
ea2f053630 feat: add fields like Email to user profile in JWT-Empty mode 2023-11-09 20:20:42 +08:00
988b14c6b5 Fix user's UpdatedTime in other APIs 2023-11-08 20:22:28 +08:00
54 changed files with 789 additions and 302 deletions

View File

@ -127,7 +127,7 @@ jobs:
release-and-push:
name: Release And Push
runs-on: ubuntu-latest
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
needs: [ frontend, backend, linter, e2e ]
steps:
- name: Checkout
@ -184,14 +184,14 @@ jobs:
- name: Log in to Docker Hub
uses: docker/login-action@v1
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Push to Docker Hub
uses: docker/build-push-action@v3
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with:
context: .
target: STANDARD
@ -201,7 +201,7 @@ jobs:
- name: Push All In One Version to Docker Hub
uses: docker/build-push-action@v3
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
if: github.repository == 'casbin/casdoor' && github.event_name == 'push' && steps.should_push.outputs.push=='true'
with:
context: .
target: ALLINONE

View File

@ -7,7 +7,7 @@ on:
jobs:
synchronize-with-crowdin:
runs-on: ubuntu-latest
if: github.repository == 'casdoor/casdoor' && github.event_name == 'push'
if: github.repository == 'casbin/casdoor' && github.event_name == 'push'
steps:
- name: Checkout

View File

@ -1,5 +1,5 @@
<h1 align="center" style="border-bottom: none;">📦⚡️ Casdoor</h1>
<h3 align="center">A UI-first centralized authentication / Single-Sign-On (SSO) platform based on OAuth 2.0 / OIDC.</h3>
<h3 align="center">An open-source UI-first Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML, CAS, LDAP, SCIM, WebAuthn, TOTP, MFA and RADIUS</h3>
<p align="center">
<a href="#badge">
<img alt="semantic-release" src="https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg">

View File

@ -34,6 +34,7 @@ import (
"github.com/casdoor/casdoor/proxy"
"github.com/casdoor/casdoor/util"
"github.com/google/uuid"
"golang.org/x/oauth2"
)
var (
@ -331,8 +332,6 @@ func (c *ApiController) Login() {
}
var user *object.User
var msg string
if authForm.Password == "" {
if user, err = object.GetUserByFields(authForm.Organization, authForm.Username); err != nil {
c.ResponseError(err.Error(), nil)
@ -354,20 +353,21 @@ func (c *ApiController) Login() {
}
// check result through Email or Phone
checkResult := object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
if len(checkResult) != 0 {
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, checkResult))
err = object.CheckSigninCode(user, checkDest, authForm.Code, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(fmt.Sprintf("%s - %s", verificationCodeType, err.Error()))
return
}
// disable the verification code
err := object.DisableVerificationCode(checkDest)
err = object.DisableVerificationCode(checkDest)
if err != nil {
c.ResponseError(err.Error(), nil)
return
}
} else {
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error(), nil)
return
@ -386,7 +386,8 @@ func (c *ApiController) Login() {
c.ResponseError(err.Error())
return
} else if enableCaptcha {
isHuman, err := captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
var isHuman bool
isHuman, err = captcha.VerifyCaptchaByCaptchaType(authForm.CaptchaType, authForm.CaptchaToken, authForm.ClientSecret)
if err != nil {
c.ResponseError(err.Error())
return
@ -399,13 +400,15 @@ func (c *ApiController) Login() {
}
password := authForm.Password
user, msg = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
user, err = object.CheckUserPassword(authForm.Organization, authForm.Username, password, c.GetAcceptLanguage(), enableCaptcha)
}
if msg != "" {
resp = &Response{Status: "error", Msg: msg}
if err != nil {
c.ResponseError(err.Error())
return
} else {
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error())
return
@ -416,7 +419,8 @@ func (c *ApiController) Login() {
return
}
organization, err := object.GetOrganizationByUser(user)
var organization *object.Organization
organization, err = object.GetOrganizationByUser(user)
if err != nil {
c.ResponseError(err.Error())
}
@ -461,12 +465,15 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), authForm.Application))
return
}
organization, err := object.GetOrganization(util.GetId("admin", application.Organization))
var organization *object.Organization
organization, err = object.GetOrganization(util.GetId("admin", application.Organization))
if err != nil {
c.ResponseError(c.T(err.Error()))
}
provider, err := object.GetProvider(util.GetId("admin", authForm.Provider))
var provider *object.Provider
provider, err = object.GetProvider(util.GetId("admin", authForm.Provider))
if err != nil {
c.ResponseError(err.Error())
return
@ -488,7 +495,8 @@ func (c *ApiController) Login() {
} else if provider.Category == "OAuth" || provider.Category == "Web3" {
// OAuth
idpInfo := object.FromProviderToIdpInfo(c.Ctx, provider)
idProvider, err := idp.GetIdProvider(idpInfo, authForm.RedirectUri)
var idProvider idp.IdProvider
idProvider, err = idp.GetIdProvider(idpInfo, authForm.RedirectUri)
if err != nil {
c.ResponseError(err.Error())
return
@ -506,7 +514,8 @@ func (c *ApiController) Login() {
}
// https://github.com/golang/oauth2/issues/123#issuecomment-103715338
token, err := idProvider.GetToken(authForm.Code)
var token *oauth2.Token
token, err = idProvider.GetToken(authForm.Code)
if err != nil {
c.ResponseError(err.Error())
return
@ -547,7 +556,12 @@ func (c *ApiController) Login() {
if user.IsForbidden {
c.ResponseError(c.T("check:The user is forbidden to sign in, please contact the administrator"))
}
// sync info from 3rd-party if possible
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
if err != nil {
c.ResponseError(err.Error())
return
}
resp = c.HandleLoggedIn(application, user, &authForm)
record := object.NewRecord(c.Ctx)
@ -588,14 +602,16 @@ func (c *ApiController) Login() {
}
// Handle username conflicts
tmpUser, err := object.GetUser(util.GetId(application.Organization, userInfo.Username))
var tmpUser *object.User
tmpUser, err = object.GetUser(util.GetId(application.Organization, userInfo.Username))
if err != nil {
c.ResponseError(err.Error())
return
}
if tmpUser != nil {
uid, err := uuid.NewRandom()
var uid uuid.UUID
uid, err = uuid.NewRandom()
if err != nil {
c.ResponseError(err.Error())
return
@ -606,14 +622,16 @@ func (c *ApiController) Login() {
}
properties := map[string]string{}
count, err := object.GetUserCount(application.Organization, "", "", "")
var count int64
count, err = object.GetUserCount(application.Organization, "", "", "")
if err != nil {
c.ResponseError(err.Error())
return
}
properties["no"] = strconv.Itoa(int(count + 2))
initScore, err := organization.GetInitScore()
var initScore int
initScore, err = organization.GetInitScore()
if err != nil {
c.ResponseError(fmt.Errorf(c.T("account:Get init score failed, error: %w"), err).Error())
return
@ -645,7 +663,8 @@ func (c *ApiController) Login() {
Properties: properties,
}
affected, err := object.AddUser(user)
var affected bool
affected, err = object.AddUser(user)
if err != nil {
c.ResponseError(err.Error())
return
@ -667,7 +686,7 @@ func (c *ApiController) Login() {
}
// sync info from 3rd-party if possible
_, err := object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
_, err = object.SetUserOAuthProperties(organization, user, provider.Type, userInfo)
if err != nil {
c.ResponseError(err.Error())
return
@ -703,7 +722,8 @@ func (c *ApiController) Login() {
return
}
oldUser, err := object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
var oldUser *object.User
oldUser, err = object.GetUserByField(application.Organization, provider.Type, userInfo.Id)
if err != nil {
c.ResponseError(err.Error())
return
@ -714,7 +734,8 @@ func (c *ApiController) Login() {
return
}
user, err := object.GetUser(userId)
var user *object.User
user, err = object.GetUser(userId)
if err != nil {
c.ResponseError(err.Error())
return
@ -727,7 +748,8 @@ func (c *ApiController) Login() {
return
}
isLinked, err := object.LinkUserAccount(user, provider.Type, userInfo.Id)
var isLinked bool
isLinked, err = object.LinkUserAccount(user, provider.Type, userInfo.Id)
if err != nil {
c.ResponseError(err.Error())
return
@ -740,7 +762,8 @@ func (c *ApiController) Login() {
}
}
} else if c.getMfaUserSession() != "" {
user, err := object.GetUser(c.getMfaUserSession())
var user *object.User
user, err = object.GetUser(c.getMfaUserSession())
if err != nil {
c.ResponseError(err.Error())
return
@ -773,7 +796,8 @@ func (c *ApiController) Login() {
return
}
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error())
return
@ -794,7 +818,8 @@ func (c *ApiController) Login() {
} else {
if c.GetSessionUsername() != "" {
// user already signed in to Casdoor, so let the user click the avatar button to do the quick sign-in
application, err := object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
var application *object.Application
application, err = object.GetApplication(fmt.Sprintf("admin/%s", authForm.Application))
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -65,13 +65,13 @@ func (c *ApiController) GetCerts() {
}
}
// GetGlobleCerts
// @Title GetGlobleCerts
// GetGlobalCerts
// @Title GetGlobalCerts
// @Tag Cert API
// @Description get globle certs
// @Success 200 {array} object.Cert The Response object
// @router /get-globle-certs [get]
func (c *ApiController) GetGlobleCerts() {
// @router /get-global-certs [get]
func (c *ApiController) GetGlobalCerts() {
limit := c.Input().Get("pageSize")
page := c.Input().Get("p")
field := c.Input().Get("field")
@ -80,7 +80,7 @@ func (c *ApiController) GetGlobleCerts() {
sortOrder := c.Input().Get("sortOrder")
if limit == "" || page == "" {
maskedCerts, err := object.GetMaskedCerts(object.GetGlobleCerts())
maskedCerts, err := object.GetMaskedCerts(object.GetGlobalCerts())
if err != nil {
c.ResponseError(err.Error())
return

View File

@ -163,6 +163,8 @@ func (c *ApiController) BuyProduct() {
id := c.Input().Get("id")
host := c.Ctx.Request.Host
providerName := c.Input().Get("providerName")
paymentEnv := c.Input().Get("paymentEnv")
// buy `pricingName/planName` for `paidUserName`
pricingName := c.Input().Get("pricingName")
planName := c.Input().Get("planName")
@ -187,11 +189,11 @@ func (c *ApiController) BuyProduct() {
return
}
payment, err := object.BuyProduct(id, user, providerName, pricingName, planName, host)
payment, attachInfo, err := object.BuyProduct(id, user, providerName, pricingName, planName, host, paymentEnv)
if err != nil {
c.ResponseError(err.Error())
return
}
c.ResponseOk(payment)
c.ResponseOk(payment, attachInfo)
}

View File

@ -158,10 +158,9 @@ func (c *ApiController) DeleteToken() {
// @Success 401 {object} object.TokenError The Response object
// @router api/login/oauth/access_token [post]
func (c *ApiController) GetOAuthToken() {
grantType := c.Input().Get("grant_type")
refreshToken := c.Input().Get("refresh_token")
clientId := c.Input().Get("client_id")
clientSecret := c.Input().Get("client_secret")
grantType := c.Input().Get("grant_type")
code := c.Input().Get("code")
verifier := c.Input().Get("code_verifier")
scope := c.Input().Get("scope")
@ -169,35 +168,61 @@ func (c *ApiController) GetOAuthToken() {
password := c.Input().Get("password")
tag := c.Input().Get("tag")
avatar := c.Input().Get("avatar")
refreshToken := c.Input().Get("refresh_token")
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
}
if clientId == "" {
// If clientID is empty, try to read data from RequestBody
if len(c.Ctx.Input.RequestBody) != 0 {
// If clientId is empty, try to read data from RequestBody
var tokenRequest TokenRequest
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest); err == nil {
clientId = tokenRequest.ClientId
clientSecret = tokenRequest.ClientSecret
grantType = tokenRequest.GrantType
refreshToken = tokenRequest.RefreshToken
code = tokenRequest.Code
verifier = tokenRequest.Verifier
scope = tokenRequest.Scope
username = tokenRequest.Username
password = tokenRequest.Password
tag = tokenRequest.Tag
avatar = tokenRequest.Avatar
err := json.Unmarshal(c.Ctx.Input.RequestBody, &tokenRequest)
if err == nil {
if clientId == "" {
clientId = tokenRequest.ClientId
}
if clientSecret == "" {
clientSecret = tokenRequest.ClientSecret
}
if grantType == "" {
grantType = tokenRequest.GrantType
}
if code == "" {
code = tokenRequest.Code
}
if verifier == "" {
verifier = tokenRequest.Verifier
}
if scope == "" {
scope = tokenRequest.Scope
}
if username == "" {
username = tokenRequest.Username
}
if password == "" {
password = tokenRequest.Password
}
if tag == "" {
tag = tokenRequest.Tag
}
if avatar == "" {
avatar = tokenRequest.Avatar
}
if refreshToken == "" {
refreshToken = tokenRequest.RefreshToken
}
}
}
host := c.Ctx.Request.Host
oAuthtoken, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
c.Data["json"] = oAuthtoken
c.Data["json"] = token
c.SetTokenErrorHttpStatus()
c.ServeJSON()
}

View File

@ -15,10 +15,10 @@
package controllers
type TokenRequest struct {
GrantType string `json:"grant_type"`
Code string `json:"code"`
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret"`
GrantType string `json:"grant_type"`
Code string `json:"code"`
Verifier string `json:"code_verifier"`
Scope string `json:"scope"`
Username string `json:"username"`

View File

@ -476,16 +476,16 @@ func (c *ApiController) SetPassword() {
isAdmin := c.IsAdmin()
if isAdmin {
if oldPassword != "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
}
} else if code == "" {
msg := object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if msg != "" {
c.ResponseError(msg)
err = object.CheckPassword(targetUser, oldPassword, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
return
}
}
@ -518,11 +518,11 @@ func (c *ApiController) CheckUserPassword() {
return
}
_, msg := object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
if msg == "" {
c.ResponseOk()
_, err = object.CheckUserPassword(user.Owner, user.Name, user.Password, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error())
} else {
c.ResponseError(msg)
c.ResponseOk()
}
}

1
go.mod
View File

@ -32,6 +32,7 @@ require (
github.com/go-webauthn/webauthn v0.6.0
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/uuid v1.3.1
github.com/json-iterator/go v1.1.12 // indirect
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/lestrrat-go/jwx v1.2.21
github.com/lib/pq v1.10.9

1
go.sum
View File

@ -1246,6 +1246,7 @@ github.com/google/go-tpm v0.3.3 h1:P/ZFNBZYXRxc+z7i5uyd8VP7MaDteuLZInzrH2idRGo=
github.com/google/go-tpm v0.3.3/go.mod h1:9Hyn3rgnzWF9XBWVk6ml6A6hNkbWjNFlDQL51BeghL4=
github.com/google/go-tpm-tools v0.0.0-20190906225433-1614c142f845/go.mod h1:AVfHadzbdzHo54inR2x1v640jdi1YSi3NauM2DUsxk0=
github.com/google/go-tpm-tools v0.2.0/go.mod h1:npUd03rQ60lxN7tzeBJreG38RvWwme2N1reF/eeiBk4=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=

View File

@ -31,6 +31,7 @@ type UserInfo struct {
Phone string
CountryCode string
AvatarUrl string
Extra map[string]string
}
type ProviderInfo struct {

View File

@ -186,15 +186,24 @@ func (idp *WeChatIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
id = wechatUserInfo.Openid
}
extra := make(map[string]string)
extra["wechat_unionid"] = wechatUserInfo.Openid
// For WeChat, different appId corresponds to different openId
extra[BuildWechatOpenIdKey(idp.Config.ClientID)] = wechatUserInfo.Openid
userInfo := UserInfo{
Id: id,
Username: wechatUserInfo.Nickname,
DisplayName: wechatUserInfo.Nickname,
AvatarUrl: wechatUserInfo.Headimgurl,
Extra: extra,
}
return &userInfo, nil
}
func BuildWechatOpenIdKey(appId string) string {
return fmt.Sprintf("wechat_openid_%s", appId)
}
func GetWechatOfficialAccountAccessToken(clientId string, clientSecret string) (string, error) {
accessTokenUrl := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", clientId, clientSecret)
request, err := http.NewRequest("GET", accessTokenUrl, nil)

View File

@ -16,6 +16,7 @@ package ldap
import (
"fmt"
"hash/fnv"
"log"
"github.com/casdoor/casdoor/conf"
@ -49,20 +50,20 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
if r.AuthenticationChoice() == "simple" {
bindUsername, bindOrg, err := getNameAndOrgFromDN(string(r.Name()))
if err != "" {
log.Printf("Bind failed ,ErrMsg=%s", err)
if err != nil {
log.Printf("getNameAndOrgFromDN() error: %s", err.Error())
res.SetResultCode(ldap.LDAPResultInvalidDNSyntax)
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
res.SetDiagnosticMessage(fmt.Sprintf("getNameAndOrgFromDN() error: %s", err.Error()))
w.Write(res)
return
}
bindPassword := string(r.AuthenticationSimple())
bindUser, err := object.CheckUserPassword(bindOrg, bindUsername, bindPassword, "en")
if err != "" {
if err != nil {
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
res.SetResultCode(ldap.LDAPResultInvalidCredentials)
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err.Error())
w.Write(res)
return
}
@ -78,7 +79,7 @@ func handleBind(w ldap.ResponseWriter, m *ldap.Message) {
m.Client.OrgName = bindOrg
} else {
res.SetResultCode(ldap.LDAPResultAuthMethodNotSupported)
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
res.SetDiagnosticMessage("Authentication method not supported, please use Simple Authentication")
}
w.Write(res)
}
@ -113,9 +114,14 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
}
for _, user := range users {
dn := fmt.Sprintf("cn=%s,%s", user.Name, string(r.BaseObject()))
dn := fmt.Sprintf("uid=%s,cn=%s,%s", user.Id, user.Name, string(r.BaseObject()))
e := ldap.NewSearchResultEntry(dn)
uidNumberStr := fmt.Sprintf("%v", hash(user.Name))
e.AddAttribute(message.AttributeDescription("uidNumber"), message.AttributeValue(uidNumberStr))
e.AddAttribute(message.AttributeDescription("gidNumber"), message.AttributeValue(uidNumberStr))
e.AddAttribute(message.AttributeDescription("homeDirectory"), message.AttributeValue("/home/"+user.Name))
e.AddAttribute(message.AttributeDescription("cn"), message.AttributeValue(user.Name))
e.AddAttribute(message.AttributeDescription("uid"), message.AttributeValue(user.Id))
for _, attr := range r.Attributes() {
e.AddAttribute(message.AttributeDescription(attr), getAttribute(string(attr), user))
if string(attr) == "cn" {
@ -127,3 +133,9 @@ func handleSearch(w ldap.ResponseWriter, m *ldap.Message) {
}
w.Write(res)
}
func hash(s string) uint32 {
h := fnv.New32a()
h.Write([]byte(s))
return h.Sum32()
}

View File

@ -26,7 +26,7 @@ import (
ldap "github.com/forestmgy/ldapserver"
)
func getNameAndOrgFromDN(DN string) (string, string, string) {
func getNameAndOrgFromDN(DN string) (string, string, error) {
DNFields := strings.Split(DN, ",")
params := make(map[string]string, len(DNFields))
for _, field := range DNFields {
@ -37,12 +37,12 @@ func getNameAndOrgFromDN(DN string) (string, string, string) {
}
if params["cn"] == "" {
return "", "", "please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
return "", "", fmt.Errorf("please use Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com")
}
if params["ou"] == "" {
return params["cn"], object.CasdoorOrganization, ""
return params["cn"], object.CasdoorOrganization, nil
}
return params["cn"], params["ou"], ""
return params["cn"], params["ou"], nil
}
func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
@ -50,7 +50,11 @@ func getNameAndOrgFromFilter(baseDN, filter string) (string, string, int) {
return "", "", ldap.LDAPResultInvalidDNSyntax
}
name, org, _ := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
name, org, err := getNameAndOrgFromDN(fmt.Sprintf("cn=%s,", getUsername(filter)) + baseDN)
if err != nil {
panic(err)
}
return name, org, ldap.LDAPResultSuccess
}

View File

@ -319,6 +319,9 @@ func GetMaskedApplication(application *Application, userId string) *Application
if application.OrganizationObj.DefaultPassword != "" {
application.OrganizationObj.DefaultPassword = "***"
}
if application.OrganizationObj.MasterVerificationCode != "" {
application.OrganizationObj.MasterVerificationCode = "***"
}
if application.OrganizationObj.PasswordType != "" {
application.OrganizationObj.PasswordType = "***"
}

View File

@ -87,7 +87,7 @@ func GetGlobalCertsCount(field, value string) (int64, error) {
return session.Count(&Cert{})
}
func GetGlobleCerts() ([]*Cert, error) {
func GetGlobalCerts() ([]*Cert, error) {
certs := []*Cert{}
err := ormer.Engine.Desc("created_time").Find(&certs)
if err != nil {
@ -163,6 +163,12 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
return false, err
}
}
err := cert.populateContent()
if err != nil {
return false, err
}
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(cert)
if err != nil {
return false, err
@ -172,10 +178,9 @@ func UpdateCert(id string, cert *Cert) (bool, error) {
}
func AddCert(cert *Cert) (bool, error) {
if cert.Certificate == "" || cert.PrivateKey == "" {
certificate, privateKey := generateRsaKeys(cert.BitSize, cert.ExpireInYears, cert.Name, cert.Owner)
cert.Certificate = certificate
cert.PrivateKey = privateKey
err := cert.populateContent()
if err != nil {
return false, err
}
affected, err := ormer.Engine.Insert(cert)
@ -199,6 +204,20 @@ func (p *Cert) GetId() string {
return fmt.Sprintf("%s/%s", p.Owner, p.Name)
}
func (p *Cert) populateContent() error {
if p.Certificate == "" || p.PrivateKey == "" {
certificate, privateKey, err := generateRsaKeys(p.BitSize, p.ExpireInYears, p.Name, p.Owner)
if err != nil {
return err
}
p.Certificate = certificate
p.PrivateKey = privateKey
}
return nil
}
func getCertByApplication(application *Application) (*Cert, error) {
if application.Cert != "" {
return getCertByName(application.Cert)

View File

@ -142,7 +142,7 @@ func CheckUserSignup(application *Application, organization *Organization, form
return ""
}
func checkSigninErrorTimes(user *User, lang string) string {
func checkSigninErrorTimes(user *User, lang string) error {
if user.SigninWrongTimes >= SigninWrongTimesLimit {
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
@ -150,37 +150,39 @@ func checkSigninErrorTimes(user *User, lang string) string {
// deny the login if the error times is greater than the limit and the last login time is less than the duration
if minutes > 0 {
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), minutes)
}
// reset the error times
user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, false)
return err
}
return ""
return nil
}
func CheckPassword(user *User, password string, lang string, options ...bool) string {
func CheckPassword(user *User, password string, lang string, options ...bool) error {
enableCaptcha := false
if len(options) > 0 {
enableCaptcha = options[0]
}
// check the login error times
if !enableCaptcha {
if msg := checkSigninErrorTimes(user, lang); msg != "" {
return msg
err := checkSigninErrorTimes(user, lang)
if err != nil {
return err
}
}
organization, err := GetOrganizationByUser(user)
if err != nil {
panic(err)
return err
}
if organization == nil {
return i18n.Translate(lang, "check:Organization does not exist")
return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
}
passwordType := user.PasswordType
@ -191,19 +193,17 @@ func CheckPassword(user *User, password string, lang string, options ...bool) st
if credManager != nil {
if organization.MasterPassword != "" {
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
resetUserSigninErrorTimes(user)
return ""
return resetUserSigninErrorTimes(user)
}
}
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
resetUserSigninErrorTimes(user)
return ""
return resetUserSigninErrorTimes(user)
}
return recordSigninErrorInfo(user, lang, enableCaptcha)
} else {
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
return fmt.Errorf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
}
}
@ -217,10 +217,10 @@ func CheckPasswordComplexity(user *User, password string) string {
return CheckPasswordComplexityByOrg(organization, password)
}
func checkLdapUserPassword(user *User, password string, lang string) string {
func checkLdapUserPassword(user *User, password string, lang string) error {
ldaps, err := GetLdaps(user.Owner)
if err != nil {
return err.Error()
return err
}
ldapLoginSuccess := false
@ -237,14 +237,14 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
searchResult, err := conn.Conn.Search(searchReq)
if err != nil {
return err.Error()
return err
}
if len(searchResult.Entries) == 0 {
continue
}
if len(searchResult.Entries) > 1 {
return i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
return fmt.Errorf(i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server"))
}
hit = true
@ -257,45 +257,47 @@ func checkLdapUserPassword(user *User, password string, lang string) string {
if !ldapLoginSuccess {
if !hit {
return "user not exist"
return fmt.Errorf("user not exist")
}
return i18n.Translate(lang, "check:LDAP user name or password incorrect")
return fmt.Errorf(i18n.Translate(lang, "check:LDAP user name or password incorrect"))
}
return ""
return nil
}
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, string) {
func CheckUserPassword(organization string, username string, password string, lang string, options ...bool) (*User, error) {
enableCaptcha := false
if len(options) > 0 {
enableCaptcha = options[0]
}
user, err := GetUserByFields(organization, username)
if err != nil {
panic(err)
return nil, err
}
if user == nil || user.IsDeleted {
return nil, fmt.Sprintf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
return nil, fmt.Errorf(i18n.Translate(lang, "general:The user: %s doesn't exist"), util.GetId(organization, username))
}
if user.IsForbidden {
return nil, i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator")
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator"))
}
if user.Ldap != "" {
// ONLY for ldap users
if msg := checkLdapUserPassword(user, password, lang); msg != "" {
if msg == "user not exist" {
return nil, fmt.Sprintf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
// only for LDAP users
err = checkLdapUserPassword(user, password, lang)
if err != nil {
if err.Error() == "user not exist" {
return nil, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist in LDAP server"), username)
}
return nil, msg
return nil, err
}
} else {
if msg := CheckPassword(user, password, lang, enableCaptcha); msg != "" {
return nil, msg
err = CheckPassword(user, password, lang, enableCaptcha)
if err != nil {
return nil, err
}
}
return user, ""
return user, nil
}
func CheckUserPermission(requestUserId, userId string, strict bool, lang string) (bool, error) {
@ -308,7 +310,7 @@ func CheckUserPermission(requestUserId, userId string, strict bool, lang string)
if userId != "" {
targetUser, err := GetUser(userId)
if err != nil {
panic(err)
return false, err
}
if targetUser == nil {

View File

@ -36,20 +36,23 @@ func isValidRealName(s string) bool {
return reRealName.MatchString(s)
}
func resetUserSigninErrorTimes(user *User) {
func resetUserSigninErrorTimes(user *User) error {
// if the password is correct and wrong times is not zero, reset the error times
if user.SigninWrongTimes == 0 {
return
return nil
}
user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
return err
}
func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
func recordSigninErrorInfo(user *User, lang string, options ...bool) error {
enableCaptcha := false
if len(options) > 0 {
enableCaptcha = options[0]
}
// increase failed login count
if user.SigninWrongTimes < SigninWrongTimesLimit {
user.SigninWrongTimes++
@ -61,13 +64,18 @@ func recordSigninErrorInfo(user *User, lang string, options ...bool) string {
}
// update user
UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
_, err := UpdateUser(user.GetId(), user, []string{"signin_wrong_times", "last_signin_wrong_time"}, false)
if err != nil {
return err
}
leftChances := SigninWrongTimesLimit - user.SigninWrongTimes
if leftChances == 0 && enableCaptcha {
return fmt.Sprint(i18n.Translate(lang, "check:password or code is incorrect"))
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect"))
} else if leftChances >= 0 {
return fmt.Sprintf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
return fmt.Errorf(i18n.Translate(lang, "check:password or code is incorrect, you have %d remaining chances"), leftChances)
}
// don't show the chance error message if the user has no chance left
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
return fmt.Errorf(i18n.Translate(lang, "check:You have entered the wrong password or code too many times, please wait for %d minutes and try again"), int(LastSignWrongTimeDuration.Minutes()))
}

View File

@ -51,23 +51,24 @@ type Organization struct {
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
Tags []string `xorm:"mediumtext" json:"tags"`
Languages []string `xorm:"varchar(255)" json:"languages"`
ThemeData *ThemeData `xorm:"json" json:"themeData"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
InitScore int `json:"initScore"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
Favicon string `xorm:"varchar(100)" json:"favicon"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
CountryCodes []string `xorm:"varchar(200)" json:"countryCodes"`
DefaultAvatar string `xorm:"varchar(200)" json:"defaultAvatar"`
DefaultApplication string `xorm:"varchar(100)" json:"defaultApplication"`
Tags []string `xorm:"mediumtext" json:"tags"`
Languages []string `xorm:"varchar(255)" json:"languages"`
ThemeData *ThemeData `xorm:"json" json:"themeData"`
MasterPassword string `xorm:"varchar(100)" json:"masterPassword"`
DefaultPassword string `xorm:"varchar(100)" json:"defaultPassword"`
MasterVerificationCode string `xorm:"varchar(100)" json:"masterVerificationCode"`
InitScore int `json:"initScore"`
EnableSoftDeletion bool `json:"enableSoftDeletion"`
IsProfilePublic bool `json:"isProfilePublic"`
MfaItems []*MfaItem `xorm:"varchar(300)" json:"mfaItems"`
AccountItems []*AccountItem `xorm:"varchar(5000)" json:"accountItems"`
@ -159,6 +160,9 @@ func GetMaskedOrganization(organization *Organization, errs ...error) (*Organiza
if organization.DefaultPassword != "" {
organization.DefaultPassword = "***"
}
if organization.MasterVerificationCode != "" {
organization.MasterVerificationCode = "***"
}
return organization, nil
}
@ -213,6 +217,9 @@ func UpdateOrganization(id string, organization *Organization) (bool, error) {
if organization.DefaultPassword == "***" {
session.Omit("default_password")
}
if organization.MasterVerificationCode == "***" {
session.Omit("master_verification_code")
}
affected, err := session.Update(organization)
if err != nil {

View File

@ -17,6 +17,8 @@ package object
import (
"fmt"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/pp"
"github.com/casdoor/casdoor/util"
@ -158,30 +160,28 @@ func (product *Product) getProvider(providerName string) (*Provider, error) {
return provider, nil
}
func BuyProduct(id string, user *User, providerName, pricingName, planName, host string) (*Payment, error) {
func BuyProduct(id string, user *User, providerName, pricingName, planName, host, paymentEnv string) (payment *Payment, attachInfo map[string]interface{}, err error) {
product, err := GetProduct(id)
if err != nil {
return nil, err
return nil, nil, err
}
if product == nil {
return nil, fmt.Errorf("the product: %s does not exist", id)
return nil, nil, fmt.Errorf("the product: %s does not exist", id)
}
provider, err := product.getProvider(providerName)
if err != nil {
return nil, err
return nil, nil, err
}
pProvider, err := GetPaymentProvider(provider)
if err != nil {
return nil, err
return nil, nil, err
}
owner := product.Owner
productName := product.Name
payerName := fmt.Sprintf("%s | %s", user.Name, user.DisplayName)
paymentName := fmt.Sprintf("payment_%v", util.GenerateTimeId())
productDisplayName := product.DisplayName
originFrontend, originBackend := getOriginFromHost(host)
returnUrl := fmt.Sprintf("%s/payments/%s/%s/result", originFrontend, owner, paymentName)
@ -191,26 +191,46 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
if pricingName != "" && planName != "" {
plan, err := GetPlan(util.GetId(owner, planName))
if err != nil {
return nil, err
return nil, nil, err
}
if plan == nil {
return nil, fmt.Errorf("the plan: %s does not exist", planName)
return nil, nil, fmt.Errorf("the plan: %s does not exist", planName)
}
sub := NewSubscription(owner, user.Name, plan.Name, paymentName, plan.Period)
_, err = AddSubscription(sub)
if err != nil {
return nil, err
return nil, nil, err
}
returnUrl = fmt.Sprintf("%s/buy-plan/%s/%s/result?subscription=%s", originFrontend, owner, pricingName, sub.Name)
}
}
// Create an OrderId and get the payUrl
payUrl, orderId, err := pProvider.Pay(providerName, productName, payerName, paymentName, productDisplayName, product.Price, product.Currency, returnUrl, notifyUrl)
// Create an order
payReq := &pp.PayReq{
ProviderName: providerName,
ProductName: product.Name,
PayerName: payerName,
PayerId: user.Id,
PaymentName: paymentName,
ProductDisplayName: product.DisplayName,
Price: product.Price,
Currency: product.Currency,
ReturnUrl: returnUrl,
NotifyUrl: notifyUrl,
PaymentEnv: paymentEnv,
}
// custom process for WeChat & WeChat Pay
if provider.Type == "WeChat Pay" {
payReq.PayerId, err = getUserExtraProperty(user, "WeChat", idp.BuildWechatOpenIdKey(provider.ClientId2))
if err != nil {
return nil, nil, err
}
}
payResp, err := pProvider.Pay(payReq)
if err != nil {
return nil, err
return nil, nil, err
}
// Create a Payment linked with Product and Order
payment := &Payment{
payment = &Payment{
Owner: product.Owner,
Name: paymentName,
CreatedTime: util.GetCurrentTime(),
@ -219,8 +239,8 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
Provider: provider.Name,
Type: provider.Type,
ProductName: productName,
ProductDisplayName: productDisplayName,
ProductName: product.Name,
ProductDisplayName: product.DisplayName,
Detail: product.Detail,
Tag: product.Tag,
Currency: product.Currency,
@ -228,10 +248,10 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
ReturnUrl: product.ReturnUrl,
User: user.Name,
PayUrl: payUrl,
PayUrl: payResp.PayUrl,
SuccessUrl: returnUrl,
State: pp.PaymentStateCreated,
OutOrderId: orderId,
OutOrderId: payResp.OrderId,
}
if provider.Type == "Dummy" {
@ -240,13 +260,13 @@ func BuyProduct(id string, user *User, providerName, pricingName, planName, host
affected, err := AddPayment(payment)
if err != nil {
return nil, err
return nil, nil, err
}
if !affected {
return nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
return nil, nil, fmt.Errorf("failed to add payment: %s", util.StructToJson(payment))
}
return payment, err
return payment, payResp.AttachInfo, nil
}
func ExtendProductWithProviders(product *Product) error {

View File

@ -621,25 +621,25 @@ func GetPasswordToken(application *Application, username string, password string
if err != nil {
return nil, nil, err
}
if user == nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "the user does not exist",
}, nil
}
var msg string
if user.Ldap != "" {
msg = checkLdapUserPassword(user, password, "en")
err = checkLdapUserPassword(user, password, "en")
} else {
msg = CheckPassword(user, password, "en")
err = CheckPassword(user, password, "en")
}
if msg != "" {
if err != nil {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: "invalid username or password",
ErrorDescription: fmt.Sprintf("invalid username or password: %s", err.Error()),
}, nil
}
if user.IsForbidden {
return nil, &TokenError{
Error: InvalidGrant,

View File

@ -34,6 +34,12 @@ type Claims struct {
type UserShort struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
Id string `xorm:"varchar(100) index" json:"id"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Avatar string `xorm:"varchar(500)" json:"avatar"`
Email string `xorm:"varchar(100) index" json:"email"`
Phone string `xorm:"varchar(20) index" json:"phone"`
}
type UserWithoutThirdIdp struct {
@ -144,6 +150,12 @@ func getShortUser(user *User) *UserShort {
res := &UserShort{
Owner: user.Owner,
Name: user.Name,
Id: user.Id,
DisplayName: user.DisplayName,
Avatar: user.Avatar,
Email: user.Email,
Phone: user.Phone,
}
return res
}

View File

@ -24,14 +24,14 @@ import (
"time"
)
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string) {
func generateRsaKeys(bitSize int, expireInYears int, commonName string, organization string) (string, string, error) {
// https://stackoverflow.com/questions/64104586/use-golang-to-get-rsa-key-the-same-way-openssl-genrsa
// https://stackoverflow.com/questions/43822945/golang-can-i-create-x509keypair-using-rsa-key
// Generate RSA key.
key, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
panic(err)
return "", "", err
}
// Encode private key to PKCS#1 ASN.1 PEM.
@ -54,9 +54,10 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
},
BasicConstraintsValid: true,
}
cert, err := x509.CreateCertificate(rand.Reader, &tml, &tml, &key.PublicKey, key)
if err != nil {
panic(err)
return "", "", err
}
// Generate a pem block with the certificate
@ -65,5 +66,5 @@ func generateRsaKeys(bitSize int, expireInYears int, commonName string, organiza
Bytes: cert,
})
return string(certPem), string(privateKeyPem)
return string(certPem), string(privateKeyPem), nil
}

View File

@ -23,7 +23,10 @@ import (
func TestGenerateRsaKeys(t *testing.T) {
fileId := "token_jwt_key"
certificate, privateKey := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
certificate, privateKey, err := generateRsaKeys(4096, 20, "Casdoor Cert", "Casdoor Organization")
if err != nil {
panic(err)
}
// Write certificate (aka certificate) to file.
util.WriteStringToPath(certificate, fmt.Sprintf("%s.pem", fileId))

View File

@ -664,6 +664,8 @@ func UpdateUserForAllFields(id string, user *User) (bool, error) {
}
}
user.UpdatedTime = util.GetCurrentTime()
affected, err := ormer.Engine.ID(core.PK{owner, name}).AllCols().Update(user)
if err != nil {
return false, err

View File

@ -35,11 +35,7 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
resp, err := client.Do(req)
if err != nil {
fmt.Printf("downloadImage() error for url [%s]: %s\n", url, err.Error())
if strings.Contains(err.Error(), "EOF") || strings.Contains(err.Error(), "no such host") || strings.Contains(err.Error(), "did not properly respond after a period of time") || strings.Contains(err.Error(), "unrecognized name") {
return nil, "", nil
} else {
return nil, "", err
}
return nil, "", nil
}
defer resp.Body.Close()
@ -58,6 +54,8 @@ func downloadImage(client *http.Client, url string) (*bytes.Buffer, string, erro
if strings.Contains(contentType, "text/html") {
fileExtension = ".html"
} else if contentType == "image/vnd.microsoft.icon" {
fileExtension = ".ico"
} else {
fileExtensions, err := mime.ExtensionsByType(contentType)
if err != nil {

View File

@ -186,10 +186,47 @@ func parseSize(sizes string) []int {
return nil
}
var publicEmailDomains = map[string]int{
"gmail.com": 1,
"163.com": 1,
"qq.com": 1,
"yahoo.com": 1,
"hotmail.com": 1,
"outlook.com": 1,
"icloud.com": 1,
"mail.com": 1,
"aol.com": 1,
"live.com": 1,
"yandex.com": 1,
"yahoo.co.jp": 1,
"yahoo.co.in": 1,
"yahoo.co.uk": 1,
"me.com": 1,
"msn.com": 1,
"comcast.net": 1,
"sbcglobal.net": 1,
"verizon.net": 1,
"earthlink.net": 1,
"cox.net": 1,
"rediffmail.com": 1,
"in.com": 1,
"hotmail.co.uk": 1,
"hotmail.fr": 1,
"zoho.com": 1,
"gmx.com": 1,
"gmx.de": 1,
"gmx.net": 1,
}
func isPublicEmailDomain(domain string) bool {
_, exists := publicEmailDomains[domain]
return exists
}
func getFaviconFileBuffer(client *http.Client, email string) (*bytes.Buffer, string, error) {
tokens := strings.Split(email, "@")
domain := tokens[1]
if domain == "gmail.com" || domain == "163.com" || domain == "qq.com" {
if isPublicEmailDomain(domain) {
return nil, "", nil
}

View File

@ -20,7 +20,10 @@ import (
"reflect"
"strings"
jsoniter "github.com/json-iterator/go"
"github.com/casdoor/casdoor/idp"
"github.com/casdoor/casdoor/util"
"github.com/xorm-io/core"
)
@ -110,6 +113,10 @@ func SetUserField(user *User, field string, value string) (bool, error) {
return false, err
}
if user != nil {
user.UpdatedTime = util.GetCurrentTime()
}
_, err = ormer.Engine.ID(core.PK{user.Owner, user.Name}).Cols("hash").Update(user)
if err != nil {
return false, err
@ -137,6 +144,25 @@ func setUserProperty(user *User, field string, value string) {
}
}
func getUserProperty(user *User, field string) string {
if user.Properties == nil {
return ""
}
return user.Properties[field]
}
func getUserExtraProperty(user *User, providerType, key string) (string, error) {
extraJson := getUserProperty(user, fmt.Sprintf("oauth_%s_extra", providerType))
if extraJson == "" {
return "", nil
}
extra := make(map[string]string)
if err := jsoniter.Unmarshal([]byte(extraJson), &extra); err != nil {
return "", err
}
return extra[key], nil
}
func SetUserOAuthProperties(organization *Organization, user *User, providerType string, userInfo *idp.UserInfo) (bool, error) {
if userInfo.Id != "" {
propertyName := fmt.Sprintf("oauth_%s_id", providerType)
@ -180,6 +206,27 @@ func SetUserOAuthProperties(organization *Organization, user *User, providerType
}
}
if userInfo.Extra != nil {
// Save extra info as json string
propertyName := fmt.Sprintf("oauth_%s_extra", providerType)
oldExtraJson := getUserProperty(user, propertyName)
extra := make(map[string]string)
if oldExtraJson != "" {
if err := jsoniter.Unmarshal([]byte(oldExtraJson), &extra); err != nil {
return false, err
}
}
for k, v := range userInfo.Extra {
extra[k] = v
}
newExtraJson, err := jsoniter.Marshal(extra)
if err != nil {
return false, err
}
setUserProperty(user, propertyName, string(newExtraJson))
}
return UpdateUserForAllFields(user.GetId(), user)
}

View File

@ -82,7 +82,12 @@ func IsAllowSend(user *User, remoteAddr, recordType string) error {
func SendVerificationCodeToEmail(organization *Organization, user *User, provider *Provider, remoteAddr string, dest string) error {
sender := organization.DisplayName
title := provider.Title
code := getRandomCode(6)
if organization.MasterVerificationCode != "" {
code = organization.MasterVerificationCode
}
// "You have requested a verification code at Casdoor. Here is your code: %s, please enter in 5 minutes."
content := fmt.Sprintf(provider.Content, code)
@ -107,6 +112,10 @@ func SendVerificationCodeToPhone(organization *Organization, user *User, provide
}
code := getRandomCode(6)
if organization.MasterVerificationCode != "" {
code = organization.MasterVerificationCode
}
if err := SendSms(provider, code, dest); err != nil {
return err
}
@ -156,7 +165,7 @@ func getVerificationRecord(dest string) (*VerificationRecord, error) {
return &record, nil
}
func CheckVerificationCode(dest, code, lang string) *VerifyResult {
func CheckVerificationCode(dest string, code string, lang string) *VerifyResult {
record, err := getVerificationRecord(dest)
if err != nil {
panic(err)
@ -183,32 +192,32 @@ func CheckVerificationCode(dest, code, lang string) *VerifyResult {
return &VerifyResult{VerificationSuccess, ""}
}
func DisableVerificationCode(dest string) (err error) {
func DisableVerificationCode(dest string) error {
record, err := getVerificationRecord(dest)
if record == nil || err != nil {
return
return nil
}
record.IsUsed = true
_, err = ormer.Engine.ID(core.PK{record.Owner, record.Name}).AllCols().Update(record)
return
return err
}
func CheckSigninCode(user *User, dest, code, lang string) string {
func CheckSigninCode(user *User, dest, code, lang string) error {
// check the login error times
if msg := checkSigninErrorTimes(user, lang); msg != "" {
return msg
err := checkSigninErrorTimes(user, lang)
if err != nil {
return err
}
result := CheckVerificationCode(dest, code, lang)
switch result.Code {
case VerificationSuccess:
resetUserSigninErrorTimes(user)
return ""
return resetUserSigninErrorTimes(user)
case wrongCodeError:
return recordSigninErrorInfo(user, lang)
default:
return result.Msg
return fmt.Errorf(result.Msg)
}
}

View File

@ -49,20 +49,24 @@ func NewAlipayPaymentProvider(appId string, appCertificate string, appPrivateKey
return pp, nil
}
func (pp *AlipayPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
func (pp *AlipayPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
// pp.Client.DebugSwitch = gopay.DebugOn
bm := gopay.BodyMap{}
pp.Client.SetReturnUrl(returnUrl)
pp.Client.SetNotifyUrl(notifyUrl)
bm.Set("subject", joinAttachString([]string{productName, productDisplayName, providerName}))
bm.Set("out_trade_no", paymentName)
bm.Set("total_amount", priceFloat64ToString(price))
pp.Client.SetReturnUrl(r.ReturnUrl)
pp.Client.SetNotifyUrl(r.NotifyUrl)
bm.Set("subject", joinAttachString([]string{r.ProductName, r.ProductDisplayName, r.ProviderName}))
bm.Set("out_trade_no", r.PaymentName)
bm.Set("total_amount", priceFloat64ToString(r.Price))
payUrl, err := pp.Client.TradePagePay(context.Background(), bm)
if err != nil {
return "", "", err
return nil, err
}
return payUrl, paymentName, nil
payResp := &PayResp{
PayUrl: payUrl,
OrderId: r.PaymentName,
}
return payResp, nil
}
func (pp *AlipayPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@ -21,8 +21,10 @@ func NewDummyPaymentProvider() (*DummyPaymentProvider, error) {
return pp, nil
}
func (pp *DummyPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
return returnUrl, "", nil
func (pp *DummyPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
return &PayResp{
PayUrl: r.ReturnUrl,
}, nil
}
func (pp *DummyPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@ -153,22 +153,22 @@ func (pp *GcPaymentProvider) doPost(postBytes []byte) ([]byte, error) {
return respBytes, nil
}
func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
func (pp *GcPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
payReqInfo := GcPayReqInfo{
OrderDate: util.GenerateSimpleTimeId(),
OrderNo: paymentName,
Amount: getPriceString(price),
OrderNo: r.PaymentName,
Amount: getPriceString(r.Price),
Xmpch: pp.Xmpch,
Body: productDisplayName,
ReturnUrl: returnUrl,
NotifyUrl: notifyUrl,
Remark1: payerName,
Remark2: productName,
Body: r.ProductDisplayName,
ReturnUrl: r.ReturnUrl,
NotifyUrl: r.NotifyUrl,
Remark1: r.PayerName,
Remark2: r.ProductName,
}
b, err := json.Marshal(payReqInfo)
if err != nil {
return "", "", err
return nil, err
}
body := GcRequestBody{
@ -184,36 +184,38 @@ func (pp *GcPaymentProvider) Pay(providerName string, productName string, payerN
bodyBytes, err := json.Marshal(body)
if err != nil {
return "", "", err
return nil, err
}
respBytes, err := pp.doPost(bodyBytes)
if err != nil {
return "", "", err
return nil, err
}
var respBody GcResponseBody
err = json.Unmarshal(respBytes, &respBody)
if err != nil {
return "", "", err
return nil, err
}
if respBody.ReturnCode != "SUCCESS" {
return "", "", fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
return nil, fmt.Errorf("%s: %s", respBody.ReturnCode, respBody.ReturnMsg)
}
payRespInfoBytes, err := base64.StdEncoding.DecodeString(respBody.Data)
if err != nil {
return "", "", err
return nil, err
}
var payRespInfo GcPayRespInfo
err = json.Unmarshal(payRespInfoBytes, &payRespInfo)
if err != nil {
return "", "", err
return nil, err
}
return payRespInfo.PayUrl, "", nil
payResp := &PayResp{
PayUrl: payRespInfo.PayUrl,
}
return payResp, nil
}
func (pp *GcPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@ -49,16 +49,16 @@ func NewPaypalPaymentProvider(clientID string, secret string) (*PaypalPaymentPro
return pp, nil
}
func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
func (pp *PaypalPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
// https://github.com/go-pay/gopay/blob/main/doc/paypal.md
units := make([]*paypal.PurchaseUnit, 0, 1)
unit := &paypal.PurchaseUnit{
ReferenceId: util.GetRandomString(16),
Amount: &paypal.Amount{
CurrencyCode: currency, // e.g."USD"
Value: priceFloat64ToString(price), // e.g."100.00"
CurrencyCode: r.Currency, // e.g."USD"
Value: priceFloat64ToString(r.Price), // e.g."100.00"
},
Description: joinAttachString([]string{productDisplayName, productName, providerName}),
Description: joinAttachString([]string{r.ProductDisplayName, r.ProductName, r.ProviderName}),
}
units = append(units, unit)
@ -68,23 +68,27 @@ func (pp *PaypalPaymentProvider) Pay(providerName string, productName string, pa
bm.SetBodyMap("application_context", func(b gopay.BodyMap) {
b.Set("brand_name", "Casdoor")
b.Set("locale", "en-PT")
b.Set("return_url", returnUrl)
b.Set("cancel_url", returnUrl)
b.Set("return_url", r.ReturnUrl)
b.Set("cancel_url", r.ReturnUrl)
})
ppRsp, err := pp.Client.CreateOrder(context.Background(), bm)
if err != nil {
return "", "", err
return nil, err
}
if ppRsp.Code != paypal.Success {
return "", "", errors.New(ppRsp.Error)
return nil, errors.New(ppRsp.Error)
}
// {"id":"9BR68863NE220374S","status":"CREATED",
// "links":[{"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"self","method":"GET"},
// {"href":"https://www.sandbox.paypal.com/checkoutnow?token=9BR68863NE220374S","rel":"approve","method":"GET"},
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S","rel":"update","method":"PATCH"},
// {"href":"https://api.sandbox.paypal.com/v2/checkout/orders/9BR68863NE220374S/capture","rel":"capture","method":"POST"}]}
return ppRsp.Response.Links[1].Href, ppRsp.Response.Id, nil
payResp := &PayResp{
PayUrl: ppRsp.Response.Links[1].Href,
OrderId: ppRsp.Response.Id,
}
return payResp, nil
}
func (pp *PaypalPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@ -24,6 +24,32 @@ const (
PaymentStateError PaymentState = "Error"
)
const (
PaymentEnvWechatBrowser = "WechatBrowser"
)
type PayReq struct {
ProviderName string
ProductName string
PayerName string
PayerId string
PaymentName string
ProductDisplayName string
Price float64
Currency string
ReturnUrl string
NotifyUrl string
PaymentEnv string
}
type PayResp struct {
PayUrl string
OrderId string
AttachInfo map[string]interface{}
}
type NotifyResult struct {
PaymentName string
PaymentStatus PaymentState
@ -39,7 +65,7 @@ type NotifyResult struct {
}
type PaymentProvider interface {
Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error)
Pay(req *PayReq) (*PayResp, error)
Notify(body []byte, orderId string) (*NotifyResult, error)
GetInvoice(paymentName string, personName string, personIdCard string, personEmail string, personPhone string, invoiceType string, invoiceTitle string, invoiceTaxId string) (string, error)
GetResponseError(err error) string

View File

@ -46,30 +46,30 @@ func NewStripePaymentProvider(PublishableKey, SecretKey string) (*StripePaymentP
return pp, nil
}
func (pp *StripePaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (payUrl string, orderId string, err error) {
func (pp *StripePaymentProvider) Pay(r *PayReq) (*PayResp, error) {
// Create a temp product
description := joinAttachString([]string{productName, productDisplayName, providerName})
description := joinAttachString([]string{r.ProductName, r.ProductDisplayName, r.ProviderName})
productParams := &stripe.ProductParams{
Name: stripe.String(productDisplayName),
Name: stripe.String(r.ProductDisplayName),
Description: stripe.String(description),
DefaultPriceData: &stripe.ProductDefaultPriceDataParams{
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
Currency: stripe.String(currency),
UnitAmount: stripe.Int64(priceFloat64ToInt64(r.Price)),
Currency: stripe.String(r.Currency),
},
}
sProduct, err := stripeProduct.New(productParams)
if err != nil {
return "", "", err
return nil, err
}
// Create a price for an existing product
priceParams := &stripe.PriceParams{
Currency: stripe.String(currency),
UnitAmount: stripe.Int64(priceFloat64ToInt64(price)),
Currency: stripe.String(r.Currency),
UnitAmount: stripe.Int64(priceFloat64ToInt64(r.Price)),
Product: stripe.String(sProduct.ID),
}
sPrice, err := stripePrice.New(priceParams)
if err != nil {
return "", "", err
return nil, err
}
// Create a Checkout Session
checkoutParams := &stripe.CheckoutSessionParams{
@ -80,17 +80,21 @@ func (pp *StripePaymentProvider) Pay(providerName string, productName string, pa
},
},
Mode: stripe.String(string(stripe.CheckoutSessionModePayment)),
SuccessURL: stripe.String(returnUrl),
CancelURL: stripe.String(returnUrl),
ClientReferenceID: stripe.String(paymentName),
SuccessURL: stripe.String(r.ReturnUrl),
CancelURL: stripe.String(r.ReturnUrl),
ClientReferenceID: stripe.String(r.PaymentName),
ExpiresAt: stripe.Int64(time.Now().Add(30 * time.Minute).Unix()),
}
checkoutParams.AddMetadata("product_description", description)
sCheckout, err := stripeCheckout.New(checkoutParams)
if err != nil {
return "", "", err
return nil, err
}
return sCheckout.URL, sCheckout.ID, nil
payResp := &PayResp{
PayUrl: sCheckout.URL,
OrderId: sCheckout.ID,
}
return payResp, nil
}
func (pp *StripePaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@ -63,27 +63,66 @@ func NewWechatPaymentProvider(mchId string, apiV3Key string, appId string, seria
return pp, nil
}
func (pp *WechatPaymentProvider) Pay(providerName string, productName string, payerName string, paymentName string, productDisplayName string, price float64, currency string, returnUrl string, notifyUrl string) (string, string, error) {
func (pp *WechatPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
bm := gopay.BodyMap{}
bm.Set("attach", joinAttachString([]string{productDisplayName, productName, providerName}))
desc := joinAttachString([]string{r.ProductDisplayName, r.ProductName, r.ProviderName})
bm.Set("attach", desc)
bm.Set("appid", pp.AppId)
bm.Set("description", productDisplayName)
bm.Set("notify_url", notifyUrl)
bm.Set("out_trade_no", paymentName)
bm.Set("description", r.ProductDisplayName)
bm.Set("notify_url", r.NotifyUrl)
bm.Set("out_trade_no", r.PaymentName)
bm.SetBodyMap("amount", func(bm gopay.BodyMap) {
bm.Set("total", priceFloat64ToInt64(price))
bm.Set("currency", currency)
bm.Set("total", priceFloat64ToInt64(r.Price))
bm.Set("currency", r.Currency)
})
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
if err != nil {
return "", "", err
// In Wechat browser, we use JSAPI
if r.PaymentEnv == PaymentEnvWechatBrowser {
if r.PayerId == "" {
return nil, errors.New("failed to get the payer's openid, please retry login")
}
bm.SetBodyMap("payer", func(bm gopay.BodyMap) {
bm.Set("openid", r.PayerId) // If the account is signup via Wechat, the PayerId is the Wechat OpenId e.g.oxW9O1ZDvgreSHuBSQDiQ2F055PI
})
jsapiRsp, err := pp.Client.V3TransactionJsapi(context.Background(), bm)
if err != nil {
return nil, err
}
if jsapiRsp.Code != wechat.Success {
return nil, errors.New(jsapiRsp.Error)
}
// use RSA256 to sign the pay request
params, err := pp.Client.PaySignOfJSAPI(pp.AppId, jsapiRsp.Response.PrepayId)
if err != nil {
return nil, err
}
payResp := &PayResp{
PayUrl: "",
OrderId: r.PaymentName, // Wechat can use paymentName as the OutTradeNo to query order status
AttachInfo: map[string]interface{}{
"appId": params.AppId,
"timeStamp": params.TimeStamp,
"nonceStr": params.NonceStr,
"package": params.Package,
"signType": "RSA",
"paySign": params.PaySign,
},
}
return payResp, nil
} else {
// In other case, we use NativeAPI
nativeRsp, err := pp.Client.V3TransactionNative(context.Background(), bm)
if err != nil {
return nil, err
}
if nativeRsp.Code != wechat.Success {
return nil, errors.New(nativeRsp.Error)
}
payResp := &PayResp{
PayUrl: nativeRsp.Response.CodeUrl,
OrderId: r.PaymentName, // Wechat can use paymentName as the OutTradeNo to query order status
}
return payResp, nil
}
if nativeRsp.Code != wechat.Success {
return "", "", errors.New(nativeRsp.Error)
}
return nativeRsp.Response.CodeUrl, paymentName, nil // Wechat can use paymentName as the OutTradeNo to query order status
}
func (pp *WechatPaymentProvider) Notify(body []byte, orderId string) (*NotifyResult, error) {

View File

@ -55,15 +55,18 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
password := rfc2865.UserPassword_GetString(r.Packet)
organization := rfc2865.Class_GetString(r.Packet)
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
if organization == "" {
w.Write(r.Response(radius.CodeAccessReject))
return
}
_, msg := object.CheckUserPassword(organization, username, password, "en")
if msg != "" {
_, err := object.CheckUserPassword(organization, username, password, "en")
if err != nil {
w.Write(r.Response(radius.CodeAccessReject))
return
}
w.Write(r.Response(radius.CodeAccessAccept))
}

View File

@ -83,13 +83,12 @@ func AutoSigninFilter(ctx *context.Context) {
password := ctx.Input.Query("password")
if userId != "" && password != "" && ctx.Input.Query("grant_type") == "" {
owner, name := util.GetOwnerAndNameFromId(userId)
_, msg := object.CheckUserPassword(owner, name, password, "en")
if msg != "" {
responseError(ctx, msg)
_, err = object.CheckUserPassword(owner, name, password, "en")
if err != nil {
responseError(ctx, err.Error())
return
}
setSessionUser(ctx, userId)
return
}
}

View File

@ -204,7 +204,7 @@ func initAPI() {
beego.Router("/api/run-syncer", &controllers.ApiController{}, "GET:RunSyncer")
beego.Router("/api/get-certs", &controllers.ApiController{}, "GET:GetCerts")
beego.Router("/api/get-globle-certs", &controllers.ApiController{}, "GET:GetGlobleCerts")
beego.Router("/api/get-global-certs", &controllers.ApiController{}, "GET:GetGlobalCerts")
beego.Router("/api/get-cert", &controllers.ApiController{}, "GET:GetCert")
beego.Router("/api/update-cert", &controllers.ApiController{}, "POST:UpdateCert")
beego.Router("/api/add-cert", &controllers.ApiController{}, "POST:AddCert")

View File

@ -2023,13 +2023,13 @@
}
}
},
"/api/get-globle-certs": {
"/api/get-global-certs": {
"get": {
"tags": [
"Cert API"
],
"description": "get globle certs",
"operationId": "ApiController.GetGlobleCerts",
"operationId": "ApiController.GetGlobalCerts",
"responses": {
"200": {
"description": "The Response object",

View File

@ -1311,12 +1311,12 @@ paths:
type: array
items:
$ref: '#/definitions/object.User'
/api/get-globle-certs:
/api/get-global-certs:
get:
tags:
- Cert API
description: get globle certs
operationId: ApiController.GetGlobleCerts
operationId: ApiController.GetGlobalCerts
responses:
"200":
description: The Response object

View File

@ -171,10 +171,27 @@ class CertEditPage extends React.Component {
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.cryptoAlgorithm} onChange={(value => {
this.updateCertField("cryptoAlgorithm", value);
if (value === "RS256") {
this.updateCertField("bitSize", 2048);
} else if (value === "HS256" || value === "ES256") {
this.updateCertField("bitSize", 256);
} else if (value === "ES384") {
this.updateCertField("bitSize", 384);
} else if (value === "ES521") {
this.updateCertField("bitSize", 521);
} else {
this.updateCertField("bitSize", 0);
}
this.updateCertField("certificate", "");
this.updateCertField("privateKey", "");
})}>
{
[
{id: "RS256", name: "RS256"},
{id: "RS256", name: "RS256 (RSA + SHA256)"},
{id: "HS256", name: "HS256 (HMAC + SHA256)"},
{id: "ES256", name: "ES256 (ECDSA using P-256 + SHA256)"},
{id: "ES384", name: "ES384 (ECDSA using P-384 + SHA256)"},
{id: "ES521", name: "ES521 (ECDSA using P-521 + SHA256)"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
@ -185,9 +202,15 @@ class CertEditPage extends React.Component {
{Setting.getLabel(i18next.t("cert:Bit size"), i18next.t("cert:Bit size - Tooltip"))} :
</Col>
<Col span={22} >
<InputNumber value={this.state.cert.bitSize} onChange={value => {
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.bitSize} onChange={(value => {
this.updateCertField("bitSize", value);
}} />
this.updateCertField("certificate", "");
this.updateCertField("privateKey", "");
})}>
{
Setting.getCryptoAlgorithmOptions(this.state.cert.cryptoAlgorithm).map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
@ -205,14 +228,14 @@ class CertEditPage extends React.Component {
{Setting.getLabel(i18next.t("cert:Certificate"), i18next.t("cert:Certificate - Tooltip"))} :
</Col>
<Col span={editorWidth} >
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
<Button style={{marginRight: "10px", marginBottom: "10px"}} disabled={this.state.cert.certificate === ""} onClick={() => {
copy(this.state.cert.certificate);
Setting.showMessage("success", i18next.t("cert:Certificate copied to clipboard successfully"));
}}
>
{i18next.t("cert:Copy certificate")}
</Button>
<Button type="primary" onClick={() => {
<Button type="primary" disabled={this.state.cert.certificate === ""} onClick={() => {
const blob = new Blob([this.state.cert.certificate], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "token_jwt_key.pem");
}}
@ -228,14 +251,14 @@ class CertEditPage extends React.Component {
{Setting.getLabel(i18next.t("cert:Private key"), i18next.t("cert:Private key - Tooltip"))} :
</Col>
<Col span={editorWidth} >
<Button style={{marginRight: "10px", marginBottom: "10px"}} onClick={() => {
<Button style={{marginRight: "10px", marginBottom: "10px"}} disabled={this.state.cert.privateKey === ""} onClick={() => {
copy(this.state.cert.privateKey);
Setting.showMessage("success", i18next.t("cert:Private key copied to clipboard successfully"));
}}
>
{i18next.t("cert:Copy private key")}
</Button>
<Button type="primary" onClick={() => {
<Button type="primary" disabled={this.state.cert.privateKey === ""} onClick={() => {
const blob = new Blob([this.state.cert.privateKey], {type: "text/plain;charset=utf-8"});
FileSaver.saveAs(blob, "token_jwt_key.key");
}}
@ -265,6 +288,7 @@ class CertEditPage extends React.Component {
this.props.history.push("/certs");
} else {
this.props.history.push(`/certs/${this.state.cert.owner}/${this.state.cert.name}`);
this.getCert();
}
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);

View File

@ -239,7 +239,7 @@ class CertListPage extends BaseListPage {
value = params.type;
}
this.setState({loading: true});
(Setting.isDefaultOrganizationSelected(this.props.account) ? CertBackend.getGlobleCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
(Setting.isDefaultOrganizationSelected(this.props.account) ? CertBackend.getGlobalCerts(params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder)
: CertBackend.getCerts(Setting.getRequestOrganization(this.props.account), params.pagination.current, params.pagination.pageSize, field, value, sortField, sortOrder))
.then((res) => {
this.setState({

View File

@ -323,6 +323,16 @@ class OrganizationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Master verification code"), i18next.t("general:Master verification code - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.organization.masterVerificationCode} onChange={e => {
this.updateOrganizationField("masterVerificationCode", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 19 : 2}>
{Setting.getLabel(i18next.t("organization:Init score"), i18next.t("organization:Init score - Tooltip"))} :

View File

@ -101,7 +101,7 @@ class PaymentResultPage extends React.Component {
payment: payment,
});
if (payment.state === "Created") {
if (["PayPal", "Stripe", "Alipay"].includes(payment.type)) {
if (["PayPal", "Stripe", "Alipay", "WeChat Pay"].includes(payment.type)) {
this.setState({
timeout: setTimeout(async() => {
await PaymentBackend.notifyPayment(this.state.owner, this.state.paymentName);

View File

@ -31,6 +31,7 @@ class ProductBuyPage extends React.Component {
pricingName: props?.pricingName ?? props?.match?.params?.pricingName ?? null,
planName: params.get("plan"),
userName: params.get("user"),
paymentEnv: "",
product: null,
pricing: props?.pricing ?? null,
plan: null,
@ -38,8 +39,21 @@ class ProductBuyPage extends React.Component {
};
}
getPaymentEnv() {
let env = "";
const ua = navigator.userAgent.toLocaleLowerCase();
// Only support Wechat Pay in Wechat Browser for mobile devices
if (ua.indexOf("micromessenger") !== -1 && ua.indexOf("mobile") !== -1) {
env = "WechatBrowser";
}
this.setState({
paymentEnv: env,
});
}
UNSAFE_componentWillMount() {
this.getProduct();
this.getPaymentEnv();
}
setStateAsync(state) {
@ -127,23 +141,74 @@ class ProductBuyPage extends React.Component {
return `${this.getCurrencySymbol(product)}${product?.price} (${this.getCurrencyText(product)})`;
}
// Call Weechat Pay via jsapi
onBridgeReady(attachInfo) {
const {WeixinJSBridge} = window;
// Setting.showMessage("success", "attachInfo is " + JSON.stringify(attachInfo));
this.setState({
isPlacingOrder: false,
});
WeixinJSBridge.invoke(
"getBrandWCPayRequest", {
"appId": attachInfo.appId,
"timeStamp": attachInfo.timeStamp,
"nonceStr": attachInfo.nonceStr,
"package": attachInfo.package,
"signType": attachInfo.signType,
"paySign": attachInfo.paySign,
},
function(res) {
if (res.err_msg === "get_brand_wcpay_request:ok") {
Setting.goToLink(attachInfo.payment.successUrl);
return ;
} else {
if (res.err_msg === "get_brand_wcpay_request:cancel") {
Setting.showMessage("error", i18next.t("product:Payment cancelled"));
} else {
Setting.showMessage("error", i18next.t("product:Payment failed"));
}
}
}
);
}
// In Wechat browser, call this function to pay via jsapi
callWechatPay(attachInfo) {
const {WeixinJSBridge} = window;
if (typeof WeixinJSBridge === "undefined") {
if (document.addEventListener) {
document.addEventListener("WeixinJSBridgeReady", () => this.onBridgeReady(attachInfo), false);
} else if (document.attachEvent) {
document.attachEvent("WeixinJSBridgeReady", () => this.onBridgeReady(attachInfo));
document.attachEvent("onWeixinJSBridgeReady", () => this.onBridgeReady(attachInfo));
}
} else {
this.onBridgeReady(attachInfo);
}
}
buyProduct(product, provider) {
this.setState({
isPlacingOrder: true,
});
ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "")
ProductBackend.buyProduct(product.owner, product.name, provider.name, this.state.pricingName ?? "", this.state.planName ?? "", this.state.userName ?? "", this.state.paymentEnv)
.then((res) => {
if (res.status === "ok") {
const payment = res.data;
const attachInfo = res.data2;
let payUrl = payment.payUrl;
if (provider.type === "WeChat Pay") {
if (this.state.paymentEnv === "WechatBrowser") {
attachInfo.payment = payment;
this.callWechatPay(attachInfo);
return ;
}
payUrl = `/qrcode/${payment.owner}/${payment.name}?providerName=${provider.name}&payUrl=${encodeURI(payment.payUrl)}&successUrl=${encodeURI(payment.successUrl)}`;
}
Setting.goToLink(payUrl);
} else {
Setting.showMessage("error", `${i18next.t("general:Failed to save")}: ${res.msg}`);
this.setState({
isPlacingOrder: false,
});
@ -218,7 +283,7 @@ class ProductBuyPage extends React.Component {
return (
<div className="login-content">
<Spin spinning={this.state.isPlacingOrder} size="large" tip={i18next.t("product:Placing order...")} style={{paddingTop: "10%"}} >
<Descriptions title={<span style={{fontSize: 28}}>{i18next.t("product:Buy Product")}</span>} bordered>
<Descriptions title={<span style={Setting.isMobile() ? {fontSize: 20} : {fontSize: 28}}>{i18next.t("product:Buy Product")}</span>} bordered>
<Descriptions.Item label={i18next.t("general:Name")} span={3}>
<span style={{fontSize: 25}}>
{Setting.getLanguageText(product?.displayName)}

View File

@ -1069,6 +1069,38 @@ export function getProviderTypeOptions(category) {
}
}
export function getCryptoAlgorithmOptions(cryptoAlgorithm) {
if (cryptoAlgorithm === "RS256") {
return (
[
{id: 1024, name: "1024"},
{id: 2048, name: "2048"},
{id: 4096, name: "4096"},
]
);
} else if (cryptoAlgorithm === "HS256" || cryptoAlgorithm === "ES256") {
return (
[
{id: 256, name: "256"},
]
);
} else if (cryptoAlgorithm === "ES384") {
return (
[
{id: 384, name: "384"},
]
);
} else if (cryptoAlgorithm === "ES521") {
return (
[
{id: 521, name: "521"},
]
);
} else {
return [];
}
}
export function renderLogo(application) {
if (application === null) {
return null;

View File

@ -337,7 +337,7 @@ class LoginPage extends React.Component {
const casParams = Util.getCasParameters();
values["type"] = this.state.type;
AuthBackend.loginCas(values, casParams).then((res) => {
if (res.status === "ok") {
const loginHandler = (res) => {
let msg = "Logged in successfully. ";
if (casParams.service === "") {
// If service was not specified, Casdoor must display a message notifying the client that it has successfully initiated a single sign-on session.
@ -351,6 +351,28 @@ class LoginPage extends React.Component {
newUrl.searchParams.append("ticket", st);
window.location.href = newUrl.toString();
}
};
if (res.status === "ok") {
if (res.data === NextMfa) {
this.setState({
getVerifyTotp: () => {
return (
<MfaAuthVerifyForm
mfaProps={res.data2}
formValues={values}
authParams={casParams}
application={this.getApplicationObj()}
onFail={() => {
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
}}
onSuccess={(res) => loginHandler(res)}
/>);
},
});
} else {
loginHandler(res);
}
} else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
}
@ -361,7 +383,7 @@ class LoginPage extends React.Component {
this.populateOauthValues(values);
AuthBackend.login(values, oAuthParams)
.then((res) => {
const callback = (res) => {
const loginHandler = (res) => {
const responseType = values["type"];
if (responseType === "login") {
@ -396,12 +418,12 @@ class LoginPage extends React.Component {
<MfaAuthVerifyForm
mfaProps={res.data2}
formValues={values}
oAuthParams={oAuthParams}
authParams={oAuthParams}
application={this.getApplicationObj()}
onFail={() => {
Setting.showMessage("error", i18next.t("mfa:Verification failed"));
}}
onSuccess={(res) => callback(res)}
onSuccess={(res) => loginHandler(res)}
/>);
},
});
@ -414,7 +436,7 @@ class LoginPage extends React.Component {
const sub = res.data2;
Setting.goToLink(`/buy-plan/${sub.owner}/${sub.pricing}/result?subscription=${sub.name}`);
} else {
callback(res);
loginHandler(res);
}
} else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);

View File

@ -262,7 +262,7 @@ class PromptPage extends React.Component {
initSteps(user, application) {
const steps = [];
if (!Setting.isPromptAnswered(user, application) && this.state.promptType === "provider") {
if (Setting.hasPromptPage(application)) {
steps.push({
content: this.renderPromptProvider(application),
name: "provider",

View File

@ -382,7 +382,7 @@ export function getAuthUrl(application, provider, method) {
let redirectUri = `${window.location.origin}/callback`;
const scope = authInfo[provider.type].scope;
const isShortState = provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger");
const isShortState = (provider.type === "WeChat" && navigator.userAgent.includes("MicroMessenger")) || (provider.type === "Twitter");
const state = Util.getStateFromQueryParams(application.name, provider.name, method, isShortState);
const codeChallenge = "P3S-a7dr8bgM4bF6vOyiKkKETDl16rcAzao9F8UIL1Y"; // SHA256(Base64-URL-encode("casdoor-verifier"))

View File

@ -24,7 +24,7 @@ import MfaVerifyTotpForm from "./MfaVerifyTotpForm";
export const NextMfa = "NextMfa";
export const RequiredMfa = "RequiredMfa";
export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, application, onSuccess, onFail}) {
export function MfaAuthVerifyForm({formValues, authParams, mfaProps, application, onSuccess, onFail}) {
formValues.password = "";
formValues.username = "";
const [loading, setLoading] = useState(false);
@ -34,7 +34,8 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
const verify = ({passcode}) => {
setLoading(true);
const values = {...formValues, passcode, mfaType};
AuthBackend.login(values, oAuthParams).then((res) => {
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
loginFunction(values, authParams).then((res) => {
if (res.status === "ok") {
onSuccess(res);
} else {
@ -49,7 +50,9 @@ export function MfaAuthVerifyForm({formValues, oAuthParams, mfaProps, applicatio
const recover = () => {
setLoading(true);
AuthBackend.login({...formValues, recoveryCode}, oAuthParams).then(res => {
const values = {...formValues, recoveryCode};
const loginFunction = formValues.type === "cas" ? AuthBackend.loginCas : AuthBackend.login;
loginFunction(values, authParams).then((res) => {
if (res.status === "ok") {
onSuccess(res);
} else {

View File

@ -24,8 +24,8 @@ export function getCerts(owner, page = "", pageSize = "", field = "", value = ""
}).then(res => res.json());
}
export function getGlobleCerts(page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-globle-certs?&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
export function getGlobalCerts(page = "", pageSize = "", field = "", value = "", sortField = "", sortOrder = "") {
return fetch(`${Setting.ServerUrl}/api/get-global-certs?&p=${page}&pageSize=${pageSize}&field=${field}&value=${value}&sortField=${sortField}&sortOrder=${sortOrder}`, {
method: "GET",
credentials: "include",
headers: {

View File

@ -70,8 +70,8 @@ export function deleteProduct(product) {
}).then(res => res.json());
}
export function buyProduct(owner, name, providerName, pricingName = "", planName = "", userName = "") {
return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerName=${providerName}&pricingName=${pricingName}&planName=${planName}&userName=${userName}`, {
export function buyProduct(owner, name, providerName, pricingName = "", planName = "", userName = "", paymentEnv = "") {
return fetch(`${Setting.ServerUrl}/api/buy-product?id=${owner}/${encodeURIComponent(name)}&providerName=${providerName}&pricingName=${pricingName}&planName=${planName}&userName=${userName}&paymentEnv=${paymentEnv}`, {
method: "POST",
credentials: "include",
headers: {