mirror of
https://github.com/casdoor/casdoor.git
synced 2025-08-02 18:50:32 +08:00
Compare commits
5 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
dff65eee20 | ||
![]() |
596016456c | ||
![]() |
673261c258 | ||
![]() |
3c5985a3c0 | ||
![]() |
4f3d62520a |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -114,12 +114,12 @@ jobs:
|
||||
wait-on-timeout: 210
|
||||
working-directory: ./web
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: failure()
|
||||
with:
|
||||
name: cypress-screenshots
|
||||
path: ./web/cypress/screenshots
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: cypress-videos
|
||||
|
@@ -561,8 +561,9 @@ func (c *ApiController) SetPassword() {
|
||||
targetUser.Password = newPassword
|
||||
targetUser.UpdateUserPassword(organization)
|
||||
targetUser.NeedUpdatePassword = false
|
||||
targetUser.LastChangePasswordTime = util.GetCurrentTime()
|
||||
|
||||
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type"}, false)
|
||||
_, err = object.UpdateUser(userId, targetUser, []string{"password", "need_update_password", "password_type", "last_change_password_time"}, false)
|
||||
if err != nil {
|
||||
c.ResponseError(err.Error())
|
||||
return
|
||||
|
@@ -381,7 +381,13 @@ func CheckUserPassword(organization string, username string, password string, la
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkPasswordExpired(user, lang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
|
53
object/check_password_expired.go
Normal file
53
object/check_password_expired.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2024 The Casdoor Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package object
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/casdoor/casdoor/i18n"
|
||||
"github.com/casdoor/casdoor/util"
|
||||
)
|
||||
|
||||
func checkPasswordExpired(user *User, lang string) error {
|
||||
organization, err := GetOrganizationByUser(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if organization == nil {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Organization does not exist"))
|
||||
}
|
||||
|
||||
passwordExpireDays := organization.PasswordExpireDays
|
||||
if passwordExpireDays <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
lastChangePasswordTime := user.LastChangePasswordTime
|
||||
if lastChangePasswordTime == "" {
|
||||
if user.CreatedTime == "" {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Your password has expired. Please reset your password by clicking \"Forgot password\""))
|
||||
}
|
||||
lastChangePasswordTime = user.CreatedTime
|
||||
}
|
||||
|
||||
lastTime := util.String2Time(lastChangePasswordTime)
|
||||
expireTime := lastTime.AddDate(0, 0, passwordExpireDays)
|
||||
if time.Now().After(expireTime) {
|
||||
return fmt.Errorf(i18n.Translate(lang, "check:Your password has expired. Please reset your password by clicking \"Forgot password\""))
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -26,6 +26,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/beevik/etree"
|
||||
@@ -276,18 +277,24 @@ func GetSamlMeta(application *Application, host string, enablePostBinding bool)
|
||||
func GetSamlResponse(application *Application, user *User, samlRequest string, host string) (string, string, string, error) {
|
||||
// request type
|
||||
method := "GET"
|
||||
|
||||
samlRequest = strings.ReplaceAll(samlRequest, " ", "+")
|
||||
// base64 decode
|
||||
defated, err := base64.StdEncoding.DecodeString(samlRequest)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("err: Failed to decode SAML request, %s", err.Error())
|
||||
}
|
||||
|
||||
var requestByte []byte
|
||||
|
||||
if strings.Contains(string(defated), "xmlns:") {
|
||||
requestByte = defated
|
||||
} else {
|
||||
// decompress
|
||||
var buffer bytes.Buffer
|
||||
rdr := flate.NewReader(bytes.NewReader(defated))
|
||||
|
||||
for {
|
||||
|
||||
_, err = io.CopyN(&buffer, rdr, 1024)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
@@ -297,8 +304,11 @@ func GetSamlResponse(application *Application, user *User, samlRequest string, h
|
||||
}
|
||||
}
|
||||
|
||||
requestByte = buffer.Bytes()
|
||||
}
|
||||
|
||||
var authnRequest saml.AuthNRequest
|
||||
err = xml.Unmarshal(buffer.Bytes(), &authnRequest)
|
||||
err = xml.Unmarshal(requestByte, &authnRequest)
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("err: Failed to unmarshal AuthnRequest, please check the SAML request, %s", err.Error())
|
||||
}
|
||||
|
@@ -200,6 +200,7 @@ type User struct {
|
||||
Permissions []*Permission `json:"permissions"`
|
||||
Groups []string `xorm:"groups varchar(1000)" json:"groups"`
|
||||
|
||||
LastChangePasswordTime string `xorm:"varchar(100)" json:"lastChangePasswordTime"`
|
||||
LastSigninWrongTime string `xorm:"varchar(100)" json:"lastSigninWrongTime"`
|
||||
SigninWrongTimes int `json:"signinWrongTimes"`
|
||||
|
||||
@@ -690,7 +691,7 @@ func UpdateUser(id string, user *User, columns []string, isAdmin bool) (bool, er
|
||||
"owner", "display_name", "avatar", "first_name", "last_name",
|
||||
"location", "address", "country_code", "region", "language", "affiliation", "title", "id_card_type", "id_card", "homepage", "bio", "tag", "language", "gender", "birthday", "education", "score", "karma", "ranking", "signup_application",
|
||||
"is_admin", "is_forbidden", "is_deleted", "hash", "is_default_avatar", "properties", "webauthnCredentials", "managedAccounts", "face_ids", "mfaAccounts",
|
||||
"signin_wrong_times", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
||||
"signin_wrong_times", "last_change_password_time", "last_signin_wrong_time", "groups", "access_key", "access_secret", "mfa_phone_enabled", "mfa_email_enabled",
|
||||
"github", "google", "qq", "wechat", "facebook", "dingtalk", "weibo", "gitee", "linkedin", "wecom", "lark", "gitlab", "adfs",
|
||||
"baidu", "alipay", "casdoor", "infoflow", "apple", "azuread", "azureadb2c", "slack", "steam", "bilibili", "okta", "douyin", "line", "amazon",
|
||||
"auth0", "battlenet", "bitbucket", "box", "cloudfoundry", "dailymotion", "deezer", "digitalocean", "discord", "dropbox",
|
||||
|
@@ -198,11 +198,11 @@ function ManagementPage(props) {
|
||||
</div>
|
||||
</Tooltip>
|
||||
<OpenTour />
|
||||
{Setting.isAdminUser(props.account) && !Setting.isMobile() && (props.uri.indexOf("/trees") === -1) &&
|
||||
{Setting.isAdminUser(props.account) && (props.uri.indexOf("/trees") === -1) &&
|
||||
<OrganizationSelect
|
||||
initValue={Setting.getOrganization()}
|
||||
withAll={true}
|
||||
style={{marginRight: "20px", width: "180px", display: "flex"}}
|
||||
style={{marginRight: "20px", width: "180px", display: !Setting.isMobile() ? "flex" : "none"}}
|
||||
onChange={(value) => {
|
||||
Setting.setOrganization(value);
|
||||
}}
|
||||
|
@@ -1009,6 +1009,19 @@ class UserEditPage extends React.Component {
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Last change password time") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
|
||||
{Setting.getLabel(i18next.t("user:Last change password time"), i18next.t("user:Last change password time"))} :
|
||||
</Col>
|
||||
<Col span={22}>
|
||||
<Input value={this.state.user.lastChangePasswordTime} onChange={e => {
|
||||
this.updateUserField("lastChangePasswordTime", e.target.value);
|
||||
}} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
} else if (accountItem.name === "Managed accounts") {
|
||||
return (
|
||||
<Row style={{marginTop: "20px"}} >
|
||||
|
@@ -243,7 +243,10 @@ class LoginPage extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
getPlaceholder() {
|
||||
getPlaceholder(defaultPlaceholder = null) {
|
||||
if (defaultPlaceholder) {
|
||||
return defaultPlaceholder;
|
||||
}
|
||||
switch (this.state.loginMethod) {
|
||||
case "verificationCode": return i18next.t("login:Email or phone");
|
||||
case "verificationCodeEmail": return i18next.t("login:Email");
|
||||
@@ -679,7 +682,7 @@ class LoginPage extends React.Component {
|
||||
id="input"
|
||||
className="login-username-input"
|
||||
prefix={<UserOutlined className="site-form-item-icon" />}
|
||||
placeholder={this.getPlaceholder()}
|
||||
placeholder={this.getPlaceholder(signinItem.placeholder)}
|
||||
onChange={e => {
|
||||
this.setState({
|
||||
username: e.target.value,
|
||||
@@ -1093,7 +1096,7 @@ class LoginPage extends React.Component {
|
||||
className="login-password-input"
|
||||
prefix={<LockOutlined className="site-form-item-icon" />}
|
||||
type="password"
|
||||
placeholder={i18next.t("general:Password")}
|
||||
placeholder={signinItem.placeholder ? signinItem.placeholder : i18next.t("general:Password")}
|
||||
disabled={this.state.loginMethod === "password" ? !Setting.isPasswordEnabled(application) : !Setting.isLdapEnabled(application)}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
@@ -113,6 +113,9 @@ export function getCasLoginParameters(owner, name) {
|
||||
|
||||
export function getOAuthGetParameters(params) {
|
||||
const queries = (params !== undefined) ? params : new URLSearchParams(window.location.search);
|
||||
const lowercaseQueries = {};
|
||||
queries.forEach((val, key) => {lowercaseQueries[key.toLowerCase()] = val;});
|
||||
|
||||
const clientId = getRefinedValue(queries.get("client_id"));
|
||||
const responseType = getRefinedValue(queries.get("response_type"));
|
||||
|
||||
@@ -138,9 +141,9 @@ export function getOAuthGetParameters(params) {
|
||||
const nonce = getRefinedValue(queries.get("nonce"));
|
||||
const challengeMethod = getRefinedValue(queries.get("code_challenge_method"));
|
||||
const codeChallenge = getRefinedValue(queries.get("code_challenge"));
|
||||
const samlRequest = getRefinedValue(queries.get("SAMLRequest"));
|
||||
const relayState = getRefinedValue(queries.get("RelayState"));
|
||||
const noRedirect = getRefinedValue(queries.get("noRedirect"));
|
||||
const samlRequest = getRefinedValue(lowercaseQueries["samlRequest".toLowerCase()]);
|
||||
const relayState = getRefinedValue(lowercaseQueries["RelayState".toLowerCase()]);
|
||||
const noRedirect = getRefinedValue(lowercaseQueries["noRedirect".toLowerCase()]);
|
||||
|
||||
if (clientId === "" && samlRequest === "") {
|
||||
// login
|
||||
|
Reference in New Issue
Block a user