mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-02 18:50:32 +08:00
Compare commits
6 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bf730050d5 | ||
![]() |
5b733b7f15 | ||
![]() |
034f28def9 | ||
![]() |
c86ac8e6ad | ||
![]() |
d647eed22a | ||
![]() |
717c53f6e5 |
@@ -77,6 +77,7 @@ p, *, *, POST, /api/verify-code, *, *
|
||||
p, *, *, POST, /api/reset-email-or-phone, *, *
|
||||
p, *, *, POST, /api/upload-resource, *, *
|
||||
p, *, *, GET, /.well-known/openid-configuration, *, *
|
||||
p, *, *, GET, /.well-known/webfinger, *, *
|
||||
p, *, *, *, /.well-known/jwks, *, *
|
||||
p, *, *, GET, /api/get-saml-login, *, *
|
||||
p, *, *, POST, /api/acs, *, *
|
||||
|
@@ -23,6 +23,7 @@ isDemoMode = false
|
||||
batchSize = 100
|
||||
enableErrorMask = false
|
||||
enableGzip = true
|
||||
inactiveTimeoutMinutes =
|
||||
ldapServerPort = 389
|
||||
radiusServerPort = 1812
|
||||
radiusSecret = "secret"
|
||||
|
@@ -14,7 +14,11 @@
|
||||
|
||||
package controllers
|
||||
|
||||
import "github.com/casdoor/casdoor/object"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/casdoor/casdoor/object"
|
||||
)
|
||||
|
||||
// GetOidcDiscovery
|
||||
// @Title GetOidcDiscovery
|
||||
@@ -42,3 +46,31 @@ func (c *RootController) GetJwks() {
|
||||
c.Data["json"] = jwks
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetWebFinger
|
||||
// @Title GetWebFinger
|
||||
// @Tag OIDC API
|
||||
// @Param resource query string true "resource"
|
||||
// @Success 200 {object} object.WebFinger
|
||||
// @router /.well-known/webfinger [get]
|
||||
func (c *RootController) GetWebFinger() {
|
||||
resource := c.Input().Get("resource")
|
||||
rels := []string{}
|
||||
host := c.Ctx.Request.Host
|
||||
|
||||
for key, value := range c.Input() {
|
||||
if strings.HasPrefix(key, "rel") {
|
||||
rels = append(rels, value...)
|
||||
}
|
||||
}
|
||||
|
||||
webfinger, err := object.GetWebFinger(resource, rels, host)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Data["json"] = webfinger
|
||||
c.Ctx.Output.ContentType("application/jrd+json")
|
||||
c.ServeJSON()
|
||||
}
|
||||
|
@@ -410,6 +410,12 @@ func (c *ApiController) GetEmailAndPhone() {
|
||||
organization := c.Ctx.Request.Form.Get("organization")
|
||||
username := c.Ctx.Request.Form.Get("username")
|
||||
|
||||
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||
if enableErrorMask2 {
|
||||
c.ResponseError("Error")
|
||||
return
|
||||
}
|
||||
|
||||
user, err := object.GetUserByFields(organization, username)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
|
@@ -45,6 +45,15 @@ func (c *ApiController) ResponseOk(data ...interface{}) {
|
||||
|
||||
// ResponseError ...
|
||||
func (c *ApiController) ResponseError(error string, data ...interface{}) {
|
||||
enableErrorMask2 := conf.GetConfigBool("enableErrorMask2")
|
||||
if enableErrorMask2 {
|
||||
error = c.T("subscription:Error")
|
||||
|
||||
resp := &Response{Status: "error", Msg: error}
|
||||
c.ResponseJsonData(resp, data...)
|
||||
return
|
||||
}
|
||||
|
||||
enableErrorMask := conf.GetConfigBool("enableErrorMask")
|
||||
if enableErrorMask {
|
||||
if strings.HasPrefix(error, "The user: ") && strings.HasSuffix(error, " doesn't exist") || strings.HasPrefix(error, "用户: ") && strings.HasSuffix(error, "不存在") {
|
||||
|
@@ -200,7 +200,7 @@ func (idp *AlipayIdProvider) postWithBody(body interface{}, targetUrl string) ([
|
||||
|
||||
formData.Set("sign", sign)
|
||||
|
||||
resp, err := idp.Client.PostForm(targetUrl, formData)
|
||||
resp, err := idp.Client.Post(targetUrl, "application/x-www-form-urlencoded;charset=utf-8", strings.NewReader(formData.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
1
main.go
1
main.go
@@ -56,6 +56,7 @@ func main() {
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.AutoSigninFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.CorsFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.TimeoutFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.ApiFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.PrometheusFilter)
|
||||
beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage)
|
||||
|
@@ -44,6 +44,18 @@ type OidcDiscovery struct {
|
||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||
}
|
||||
|
||||
type WebFinger struct {
|
||||
Subject string `json:"subject"`
|
||||
Links []WebFingerLink `json:"links"`
|
||||
Aliases *[]string `json:"aliases,omitempty"`
|
||||
Properties *map[string]string `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
type WebFingerLink struct {
|
||||
Rel string `json:"rel"`
|
||||
Href string `json:"href"`
|
||||
}
|
||||
|
||||
func isIpAddress(host string) bool {
|
||||
// Attempt to split the host and port, ignoring the error
|
||||
hostWithoutPort, _, err := net.SplitHostPort(host)
|
||||
@@ -160,3 +172,43 @@ func GetJsonWebKeySet() (jose.JSONWebKeySet, error) {
|
||||
|
||||
return jwks, nil
|
||||
}
|
||||
|
||||
func GetWebFinger(resource string, rels []string, host string) (WebFinger, error) {
|
||||
wf := WebFinger{}
|
||||
|
||||
resourceSplit := strings.Split(resource, ":")
|
||||
|
||||
if len(resourceSplit) != 2 {
|
||||
return wf, fmt.Errorf("invalid resource")
|
||||
}
|
||||
|
||||
resourceType := resourceSplit[0]
|
||||
resourceValue := resourceSplit[1]
|
||||
|
||||
oidcDiscovery := GetOidcDiscovery(host)
|
||||
|
||||
switch resourceType {
|
||||
case "acct":
|
||||
user, err := GetUserByEmailOnly(resourceValue)
|
||||
if err != nil {
|
||||
return wf, err
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
return wf, fmt.Errorf("user not found")
|
||||
}
|
||||
|
||||
wf.Subject = resource
|
||||
|
||||
for _, rel := range rels {
|
||||
if rel == "http://openid.net/specs/connect/1.0/issuer" {
|
||||
wf.Links = append(wf.Links, WebFingerLink{
|
||||
Rel: "http://openid.net/specs/connect/1.0/issuer",
|
||||
Href: oidcDiscovery.Issuer,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return wf, nil
|
||||
}
|
||||
|
@@ -56,7 +56,7 @@ type Organization struct {
|
||||
WebsiteUrl string `xorm:"varchar(100)" json:"websiteUrl"`
|
||||
Logo string `xorm:"varchar(200)" json:"logo"`
|
||||
LogoDark string `xorm:"varchar(200)" json:"logoDark"`
|
||||
Favicon string `xorm:"varchar(100)" json:"favicon"`
|
||||
Favicon string `xorm:"varchar(200)" json:"favicon"`
|
||||
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
|
||||
PasswordSalt string `xorm:"varchar(100)" json:"passwordSalt"`
|
||||
PasswordOptions []string `xorm:"varchar(100)" json:"passwordOptions"`
|
||||
|
@@ -166,19 +166,76 @@ func AddToVerificationRecord(user *User, provider *Provider, remoteAddr, recordT
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterRecordIn24Hours(record *VerificationRecord) *VerificationRecord {
|
||||
if record == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
if now-record.Time > 60*60*24 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
func getVerificationRecord(dest string) (*VerificationRecord, error) {
|
||||
var record VerificationRecord
|
||||
record := &VerificationRecord{}
|
||||
record.Receiver = dest
|
||||
|
||||
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(&record)
|
||||
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
record = filterRecordIn24Hours(record)
|
||||
if record == nil {
|
||||
has = false
|
||||
}
|
||||
|
||||
if !has {
|
||||
record = &VerificationRecord{}
|
||||
record.Receiver = dest
|
||||
|
||||
has, err = ormer.Engine.Desc("time").Get(record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
record = filterRecordIn24Hours(record)
|
||||
if record == nil {
|
||||
has = false
|
||||
}
|
||||
|
||||
if !has {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func getUnusedVerificationRecord(dest string) (*VerificationRecord, error) {
|
||||
record := &VerificationRecord{}
|
||||
record.Receiver = dest
|
||||
|
||||
has, err := ormer.Engine.Desc("time").Where("is_used = false").Get(record)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
record = filterRecordIn24Hours(record)
|
||||
if record == nil {
|
||||
has = false
|
||||
}
|
||||
|
||||
if !has {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &record, nil
|
||||
return record, nil
|
||||
}
|
||||
|
||||
func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult, error) {
|
||||
@@ -187,7 +244,9 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
||||
return nil, err
|
||||
}
|
||||
if record == nil {
|
||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet, or has already been used!")}, nil
|
||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet!")}, nil
|
||||
} else if record.IsUsed {
|
||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has already been used!")}, nil
|
||||
}
|
||||
|
||||
timeoutInMinutes, err := conf.GetConfigInt64("verificationCodeTimeout")
|
||||
@@ -196,9 +255,6 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
if now-record.Time > timeoutInMinutes*60*10 {
|
||||
return &VerifyResult{noRecordError, i18n.Translate(lang, "verification:The verification code has not been sent yet!")}, nil
|
||||
}
|
||||
if now-record.Time > timeoutInMinutes*60 {
|
||||
return &VerifyResult{timeoutError, fmt.Sprintf(i18n.Translate(lang, "verification:You should verify your code in %d min!"), timeoutInMinutes)}, nil
|
||||
}
|
||||
@@ -211,7 +267,7 @@ func CheckVerificationCode(dest string, code string, lang string) (*VerifyResult
|
||||
}
|
||||
|
||||
func DisableVerificationCode(dest string) error {
|
||||
record, err := getVerificationRecord(dest)
|
||||
record, err := getUnusedVerificationRecord(dest)
|
||||
if record == nil || err != nil {
|
||||
return nil
|
||||
}
|
||||
|
@@ -290,6 +290,7 @@ func initAPI() {
|
||||
|
||||
beego.Router("/.well-known/openid-configuration", &controllers.RootController{}, "GET:GetOidcDiscovery")
|
||||
beego.Router("/.well-known/jwks", &controllers.RootController{}, "*:GetJwks")
|
||||
beego.Router("/.well-known/webfinger", &controllers.RootController{}, "GET:GetWebFinger")
|
||||
|
||||
beego.Router("/cas/:organization/:application/serviceValidate", &controllers.RootController{}, "GET:CasServiceValidate")
|
||||
beego.Router("/cas/:organization/:application/proxyValidate", &controllers.RootController{}, "GET:CasProxyValidate")
|
||||
|
64
routers/timeout_filter.go
Normal file
64
routers/timeout_filter.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// 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 routers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/beego/beego/context"
|
||||
"github.com/casdoor/casdoor/conf"
|
||||
)
|
||||
|
||||
var (
|
||||
inactiveTimeoutMinutes int64
|
||||
requestTimeMap sync.Map
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
inactiveTimeoutMinutes, err = conf.GetConfigInt64("inactiveTimeoutMinutes")
|
||||
if err != nil {
|
||||
inactiveTimeoutMinutes = 0
|
||||
}
|
||||
}
|
||||
|
||||
func timeoutLogout(ctx *context.Context, sessionId string) {
|
||||
requestTimeMap.Delete(sessionId)
|
||||
ctx.Input.CruSession.Set("username", "")
|
||||
ctx.Input.CruSession.Set("accessToken", "")
|
||||
ctx.Input.CruSession.Delete("SessionData")
|
||||
responseError(ctx, fmt.Sprintf(T(ctx, "auth:Timeout for inactivity of %d minutes"), inactiveTimeoutMinutes))
|
||||
}
|
||||
|
||||
func TimeoutFilter(ctx *context.Context) {
|
||||
if inactiveTimeoutMinutes <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
owner, name := getSubject(ctx)
|
||||
if owner == "anonymous" || name == "anonymous" {
|
||||
return
|
||||
}
|
||||
|
||||
sessionId := ctx.Input.CruSession.SessionID()
|
||||
currentTime := time.Now()
|
||||
preRequestTime, has := requestTimeMap.Load(sessionId)
|
||||
requestTimeMap.Store(sessionId, currentTime)
|
||||
if has && preRequestTime.(time.Time).Add(time.Minute*time.Duration(inactiveTimeoutMinutes)).Before(currentTime) {
|
||||
timeoutLogout(ctx, sessionId)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user