Compare commits

...

8 Commits

Author SHA1 Message Date
hsluoyz
56d0de64dc feat: support StopOldInstance() 2025-01-07 21:39:21 +08:00
DacongDA
1813e8e8c7 feat: return goroutine error in get-dashboard API (#3479) 2025-01-07 10:35:45 +08:00
DacongDA
e27c764a55 feat: fix bug that GitHub oauth provider shows error if failed to fetch user's email (#3474)
* fix: fix github idp will stop login if it cannot fetch user's email through al restful api

* Update github.go

---------

Co-authored-by: hsluoyz <hsluoyz@qq.com>
2025-01-05 20:25:42 +08:00
DacongDA
e5a2057382 feat: fix empty scope bug in RefreshToken API (#3467)
* fix: fix scope will be empty when user not passing scope in refresh api

* fix: promote code format
2025-01-02 12:53:17 +08:00
Yang Luo
8457ff7433 feat: support radiusDefaultOrganization in app.conf 2025-01-02 00:10:58 +08:00
DacongDA
888a6f2feb feat: add regex to restrict Email addresses in OAuth provider (#3465)
* feat: support use regex expression to limit email receiver address

* feat: limit in correct pos

* feat: promote code format

* feat: promote code format

* fix: fix linter issue
2025-01-02 00:00:57 +08:00
IZUMI-Zu
b57b64fc36 feat: add origin field for mfaAccountTable (#3463) 2024-12-29 22:51:21 +08:00
DacongDA
0d239ba1cf feat: improve the error message of GitHub OAuth provider (#3462) 2024-12-29 21:54:54 +08:00
12 changed files with 207 additions and 15 deletions

View File

@@ -28,6 +28,7 @@ ldapServerPort = 389
ldapsCertId = ""
ldapsServerPort = 636
radiusServerPort = 1812
radiusDefaultOrganization = "built-in"
radiusSecret = "secret"
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}

View File

@@ -22,6 +22,7 @@ import (
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
@@ -617,6 +618,17 @@ func (c *ApiController) Login() {
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
return
}
if provider.EmailRegex != "" {
reg, err := regexp.Compile(provider.EmailRegex)
if err != nil {
c.ResponseError(fmt.Sprintf(c.T("auth:Failed to login in: %s"), err.Error()))
return
}
if !reg.MatchString(userInfo.Email) {
c.ResponseError(fmt.Sprintf(c.T("check:Email is invalid")))
}
}
}
if authForm.Method == "signup" {

View File

@@ -195,6 +195,12 @@ type GitHubUserEmailInfo struct {
Visibility string `json:"visibility"`
}
type GitHubErrorInfo struct {
Message string `json:"message"`
DocumentationUrl string `json:"documentation_url"`
Status string `json:"status"`
}
func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error) {
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
if err != nil {
@@ -236,13 +242,23 @@ func (idp *GithubIdProvider) GetUserInfo(token *oauth2.Token) (*UserInfo, error)
return nil, err
}
var userEmails []GitHubUserEmailInfo
err = json.Unmarshal(emailBody, &userEmails)
if err != nil {
return nil, err
}
if respEmail.StatusCode != 200 {
var errMessage GitHubErrorInfo
err = json.Unmarshal(emailBody, &errMessage)
if err != nil {
return nil, err
}
githubUserInfo.Email = idp.getEmailFromEmailsResult(userEmails)
fmt.Printf("GithubIdProvider:GetUserInfo() error, status code = %d, error message = %v\n", respEmail.StatusCode, errMessage)
} else {
var userEmails []GitHubUserEmailInfo
err = json.Unmarshal(emailBody, &userEmails)
if err != nil {
return nil, err
}
githubUserInfo.Email = idp.getEmailFromEmailsResult(userEmails)
}
}
userInfo := UserInfo{

View File

@@ -83,6 +83,11 @@ func main() {
// logs.SetLevel(logs.LevelInformational)
logs.SetLogFuncCall(false)
err = util.StopOldInstance(port)
if err != nil {
panic(err)
}
go ldap.StartLdapServer()
go radius.StartRadiusServer()
go object.ClearThroughputPerSecond()

View File

@@ -41,11 +41,11 @@ func GetDashboard(owner string) (*map[string][]int64, error) {
var wg sync.WaitGroup
var err error
wg.Add(len(tableNames))
ch := make(chan error, len(tableNames))
for _, tableName := range tableNames {
dashboard[tableName+"Counts"] = make([]int64, 31)
tableName := tableName
go func() {
go func(ch chan error) {
defer wg.Done()
dashboardDateItems := []DashboardDateItem{}
var countResult int64
@@ -59,20 +59,29 @@ func GetDashboard(owner string) (*map[string][]int64, error) {
}
if countResult, err = dbQueryBefore.And("created_time < ?", time30day).Table(tableName).Count(); err != nil {
panic(err)
ch <- err
return
}
if err = dbQueryAfter.And("created_time >= ?", time30day).Table(tableName).Find(&dashboardDateItems); err != nil {
panic(err)
ch <- err
return
}
dashboardMap.Store(tableName, DashboardMapItem{
dashboardDateItems: dashboardDateItems,
itemCount: countResult,
})
}()
}(ch)
}
wg.Wait()
close(ch)
for err = range ch {
if err != nil {
return nil, err
}
}
nowTime := time.Now()
for i := 30; i >= 0; i-- {

View File

@@ -16,6 +16,7 @@ package object
import (
"fmt"
"regexp"
"strings"
"github.com/beego/beego/context"
@@ -70,6 +71,7 @@ type Provider struct {
IdP string `xorm:"mediumtext" json:"idP"`
IssuerUrl string `xorm:"varchar(100)" json:"issuerUrl"`
EnableSignAuthnRequest bool `json:"enableSignAuthnRequest"`
EmailRegex string `xorm:"varchar(200)" json:"emailRegex"`
ProviderUrl string `xorm:"varchar(200)" json:"providerUrl"`
}
@@ -200,6 +202,13 @@ func UpdateProvider(id string, provider *Provider) (bool, error) {
return false, nil
}
if provider.EmailRegex != "" {
_, err := regexp.Compile(provider.EmailRegex)
if err != nil {
return false, err
}
}
if name != provider.Name {
err := providerChangeTrigger(name, provider.Name)
if err != nil {
@@ -234,6 +243,13 @@ func AddProvider(provider *Provider) (bool, error) {
provider.IntranetEndpoint = util.GetEndPoint(provider.IntranetEndpoint)
}
if provider.EmailRegex != "" {
_, err := regexp.Compile(provider.EmailRegex)
if err != nil {
return false, err
}
}
affected, err := ormer.Engine.Insert(provider)
if err != nil {
return false, err

View File

@@ -309,22 +309,29 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
}, nil
}
var oldTokenScope string
if application.TokenFormat == "JWT-Standard" {
_, err = ParseStandardJwtToken(refreshToken, cert)
oldToken, err := ParseStandardJwtToken(refreshToken, cert)
if err != nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}, nil
}
oldTokenScope = oldToken.Scope
} else {
_, err = ParseJwtToken(refreshToken, cert)
oldToken, err := ParseJwtToken(refreshToken, cert)
if err != nil {
return &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("parse refresh token error: %s", err.Error()),
}, nil
}
oldTokenScope = oldToken.Scope
}
if scope == "" {
scope = oldTokenScope
}
// generate a new token

View File

@@ -237,6 +237,7 @@ type MfaAccount struct {
AccountName string `xorm:"varchar(100)" json:"accountName"`
Issuer string `xorm:"varchar(100)" json:"issuer"`
SecretKey string `xorm:"varchar(100)" json:"secretKey"`
Origin string `xorm:"varchar(100)" json:"origin"`
}
type FaceId struct {

View File

@@ -68,8 +68,10 @@ func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) {
log.Printf("handleAccessRequest() username=%v, org=%v, password=%v", username, organization, password)
if organization == "" {
w.Write(r.Response(radius.CodeAccessReject))
return
organization = conf.GetConfigString("radiusDefaultOrganization")
if organization == "" {
organization = "built-in"
}
}
var user *object.User

97
util/process.go Normal file
View File

@@ -0,0 +1,97 @@
// Copyright 2025 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 util
import (
"fmt"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
)
func getPidByPort(port int) (int, error) {
var cmd *exec.Cmd
switch runtime.GOOS {
case "windows":
cmd = exec.Command("cmd", "/c", "netstat -ano | findstr :"+strconv.Itoa(port))
case "darwin", "linux":
cmd = exec.Command("lsof", "-i", ":"+strconv.Itoa(port))
default:
return 0, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
output, err := cmd.Output()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == 1 {
return 0, nil
}
} else {
return 0, err
}
}
lines := strings.Split(string(output), "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) > 0 {
if runtime.GOOS == "windows" {
if fields[1] == "0.0.0.0:"+strconv.Itoa(port) {
pid, err := strconv.Atoi(fields[len(fields)-1])
if err != nil {
return 0, err
}
return pid, nil
}
} else {
pid, err := strconv.Atoi(fields[1])
if err != nil {
return 0, err
}
return pid, nil
}
}
}
return 0, nil
}
func StopOldInstance(port int) error {
pid, err := getPidByPort(port)
if err != nil {
return err
}
if pid == 0 {
return nil
}
process, err := os.FindProcess(pid)
if err != nil {
return err
}
err = process.Kill()
if err != nil {
return err
} else {
fmt.Printf("The old instance with pid: %d has been stopped\n", pid)
}
return nil
}

View File

@@ -633,6 +633,20 @@ class ProviderEditPage extends React.Component {
</React.Fragment>
)
}
{
this.state.provider.category === "OAuth" ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Email regex"), i18next.t("provider:Email regex - Tooltip"))} :
</Col>
<Col span={22}>
<TextArea rows={4} value={this.state.provider.emailRegex} onChange={e => {
this.updateProviderField("emailRegex", e.target.value);
}} />
</Col>
</Row>
) : null
}
{
this.state.provider.type === "Custom" ? (
<React.Fragment>

View File

@@ -105,6 +105,18 @@ class MfaAccountTable extends React.Component {
);
},
},
{
title: i18next.t("mfaAccount:Origin"),
dataIndex: "origin",
key: "origin",
render: (text, record, index) => {
return (
<Input value={text} onChange={e => {
this.updateField(table, index, "origin", e.target.value);
}} />
);
},
},
{
title: i18next.t("mfaAccount:Secret Key"),
dataIndex: "secretKey",