casdoor/object/check.go

360 lines
11 KiB
Go
Raw Normal View History

2022-02-13 23:39:27 +08:00
// Copyright 2021 The Casdoor Authors. All Rights Reserved.
2021-03-06 16:39:17 +08:00
//
// 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.
2021-02-11 22:56:08 +08:00
package object
2021-05-01 16:50:47 +08:00
import (
"fmt"
"regexp"
"strings"
"time"
"unicode"
2021-05-01 17:45:01 +08:00
2022-01-20 14:11:46 +08:00
"github.com/casdoor/casdoor/cred"
"github.com/casdoor/casdoor/i18n"
2022-01-20 14:11:46 +08:00
"github.com/casdoor/casdoor/util"
goldap "github.com/go-ldap/ldap/v3"
2021-05-01 16:50:47 +08:00
)
var (
reWhiteSpace *regexp.Regexp
reFieldWhiteList *regexp.Regexp
)
2021-05-01 16:50:47 +08:00
const (
SigninWrongTimesLimit = 5
LastSignWrongTimeDuration = time.Minute * 15
)
2021-05-01 16:50:47 +08:00
func init() {
2021-08-07 22:02:56 +08:00
reWhiteSpace, _ = regexp.Compile(`\s`)
reFieldWhiteList, _ = regexp.Compile(`^[A-Za-z0-9]+$`)
2021-05-01 16:50:47 +08:00
}
2021-02-14 01:04:51 +08:00
func CheckUserSignup(application *Application, organization *Organization, username string, password string, displayName string, firstName string, lastName string, email string, phone string, affiliation string, lang string) string {
2021-06-17 00:49:02 +08:00
if organization == nil {
return i18n.Translate(lang, "check:Organization does not exist")
2021-06-17 00:49:02 +08:00
}
if application.IsSignupItemVisible("Username") {
if len(username) <= 1 {
return i18n.Translate(lang, "check:Username must have at least 2 characters")
}
if unicode.IsDigit(rune(username[0])) {
return i18n.Translate(lang, "check:Username cannot start with a digit")
}
if util.IsEmailValid(username) {
return i18n.Translate(lang, "check:Username cannot be an email address")
}
if reWhiteSpace.MatchString(username) {
return i18n.Translate(lang, "check:Username cannot contain white spaces")
}
msg := CheckUsername(username, lang)
if msg != "" {
return msg
}
if HasUserByField(organization.Name, "name", username) {
return i18n.Translate(lang, "check:Username already exists")
2021-06-17 00:49:02 +08:00
}
if HasUserByField(organization.Name, "email", email) {
return i18n.Translate(lang, "check:Email already exists")
}
if HasUserByField(organization.Name, "phone", phone) {
return i18n.Translate(lang, "check:Phone already exists")
}
2021-06-17 00:49:02 +08:00
}
2021-05-15 13:54:23 +08:00
2021-06-17 00:49:02 +08:00
if len(password) <= 5 {
return i18n.Translate(lang, "check:Password must have at least 6 characters")
2021-02-11 22:56:08 +08:00
}
2021-06-17 00:49:02 +08:00
if application.IsSignupItemVisible("Email") {
if email == "" {
if application.IsSignupItemRequired("Email") {
return i18n.Translate(lang, "check:Email cannot be empty")
} else {
return ""
}
}
2021-06-17 00:49:02 +08:00
if HasUserByField(organization.Name, "email", email) {
return i18n.Translate(lang, "check:Email already exists")
2021-06-17 00:49:02 +08:00
} else if !util.IsEmailValid(email) {
return i18n.Translate(lang, "check:Email is invalid")
2021-06-17 00:49:02 +08:00
}
}
if application.IsSignupItemVisible("Phone") {
if phone == "" {
if application.IsSignupItemRequired("Phone") {
return i18n.Translate(lang, "check:Phone cannot be empty")
} else {
return ""
}
}
2021-06-17 00:49:02 +08:00
if HasUserByField(organization.Name, "phone", phone) {
return i18n.Translate(lang, "check:Phone already exists")
2021-06-17 00:49:02 +08:00
} else if organization.PhonePrefix == "86" && !util.IsPhoneCnValid(phone) {
return i18n.Translate(lang, "check:Phone number is invalid")
2021-06-17 00:49:02 +08:00
}
}
if application.IsSignupItemVisible("Display name") {
if application.GetSignupItemRule("Display name") == "First, last" && (firstName != "" || lastName != "") {
if firstName == "" {
return i18n.Translate(lang, "check:FirstName cannot be blank")
} else if lastName == "" {
return i18n.Translate(lang, "check:LastName cannot be blank")
}
} else {
if displayName == "" {
return i18n.Translate(lang, "check:DisplayName cannot be blank")
} else if application.GetSignupItemRule("Display name") == "Real name" {
if !isValidRealName(displayName) {
return i18n.Translate(lang, "check:DisplayName is not valid real name")
}
2021-06-17 11:55:06 +08:00
}
2021-06-17 00:49:02 +08:00
}
}
if application.IsSignupItemVisible("Affiliation") {
if affiliation == "" {
return i18n.Translate(lang, "check:Affiliation cannot be blank")
2021-06-17 00:49:02 +08:00
}
}
return ""
2021-02-11 22:56:08 +08:00
}
func checkSigninErrorTimes(user *User, lang string) string {
if user.SigninWrongTimes >= SigninWrongTimesLimit {
lastSignWrongTime, _ := time.Parse(time.RFC3339, user.LastSigninWrongTime)
passedTime := time.Now().UTC().Sub(lastSignWrongTime)
seconds := int(LastSignWrongTimeDuration.Seconds() - passedTime.Seconds())
// deny the login if the error times is greater than the limit and the last login time is less than the duration
if seconds > 0 {
return fmt.Sprintf(i18n.Translate(lang, "check:You have entered the wrong password too many times, please wait for %d minutes %d seconds and try again"), seconds/60, seconds%60)
}
// reset the error times
user.SigninWrongTimes = 0
UpdateUser(user.GetId(), user, []string{"signin_wrong_times"}, user.IsGlobalAdmin)
}
return ""
}
func CheckPassword(user *User, password string, lang string) string {
// check the login error times
if msg := checkSigninErrorTimes(user, lang); msg != "" {
return msg
}
2021-05-16 22:58:30 +08:00
organization := GetOrganizationByUser(user)
if organization == nil {
return i18n.Translate(lang, "check:Organization does not exist")
}
2021-11-04 21:08:43 +08:00
credManager := cred.GetCredManager(organization.PasswordType)
if credManager != nil {
if organization.MasterPassword != "" {
if credManager.IsPasswordCorrect(password, organization.MasterPassword, "", organization.PasswordSalt) {
resetUserSigninErrorTimes(user)
return ""
}
2021-11-06 21:14:53 +08:00
}
if credManager.IsPasswordCorrect(password, user.Password, user.PasswordSalt, organization.PasswordSalt) {
resetUserSigninErrorTimes(user)
2021-05-03 10:13:32 +08:00
return ""
}
return recordSigninErrorInfo(user, lang)
2021-05-03 10:13:32 +08:00
} else {
return fmt.Sprintf(i18n.Translate(lang, "check:unsupported password type: %s"), organization.PasswordType)
2021-05-03 10:13:32 +08:00
}
}
func checkLdapUserPassword(user *User, password string, lang string) (*User, string) {
ldaps := GetLdaps(user.Owner)
ldapLoginSuccess := false
for _, ldapServer := range ldaps {
conn, err := GetLdapConn(ldapServer.Host, ldapServer.Port, ldapServer.Admin, ldapServer.Passwd)
if err != nil {
continue
}
SearchFilter := fmt.Sprintf("(&(objectClass=posixAccount)(uid=%s))", user.Name)
searchReq := goldap.NewSearchRequest(ldapServer.BaseDn,
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
SearchFilter, []string{}, nil)
searchResult, err := conn.Conn.Search(searchReq)
if err != nil {
return nil, err.Error()
}
if len(searchResult.Entries) == 0 {
continue
} else if len(searchResult.Entries) > 1 {
return nil, i18n.Translate(lang, "check:Multiple accounts with same uid, please check your ldap server")
}
dn := searchResult.Entries[0].DN
if err := conn.Conn.Bind(dn, password); err == nil {
ldapLoginSuccess = true
break
}
}
if !ldapLoginSuccess {
return nil, i18n.Translate(lang, "check:Ldap user name or password incorrect")
}
return user, ""
}
func CheckUserPassword(organization string, username string, password string, lang string) (*User, string) {
2021-05-01 20:23:20 +08:00
user := GetUserByFields(organization, username)
2021-11-06 15:52:03 +08:00
if user == nil || user.IsDeleted == true {
return nil, i18n.Translate(lang, "check:The user doesn't exist")
2021-02-11 22:56:08 +08:00
}
2021-05-05 23:32:21 +08:00
if user.IsForbidden {
return nil, i18n.Translate(lang, "check:The user is forbidden to sign in, please contact the administrator")
2021-05-05 23:32:21 +08:00
}
if user.Ldap != "" {
// ONLY for ldap users
return checkLdapUserPassword(user, password, lang)
} else {
msg := CheckPassword(user, password, lang)
if msg != "" {
return nil, msg
}
2021-02-11 22:56:08 +08:00
}
2021-05-01 19:45:40 +08:00
return user, ""
2021-02-11 22:56:08 +08:00
}
func filterField(field string) bool {
return reFieldWhiteList.MatchString(field)
2022-02-13 23:39:27 +08:00
}
func CheckUserPermission(requestUserId, userId, userOwner string, strict bool, lang string) (bool, error) {
if requestUserId == "" {
return false, fmt.Errorf(i18n.Translate(lang, "check:Please login first"))
}
if userId != "" {
targetUser := GetUser(userId)
if targetUser == nil {
return false, fmt.Errorf(i18n.Translate(lang, "check:The user: %s doesn't exist"), userId)
}
userOwner = targetUser.Owner
}
hasPermission := false
if strings.HasPrefix(requestUserId, "app/") {
hasPermission = true
} else {
requestUser := GetUser(requestUserId)
if requestUser == nil {
return false, fmt.Errorf(i18n.Translate(lang, "check:Session outdated, please login again"))
}
if requestUser.IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
} else if userOwner == requestUser.Owner {
if strict {
hasPermission = requestUser.IsAdmin
} else {
hasPermission = true
}
}
}
return hasPermission, fmt.Errorf(i18n.Translate(lang, "check:You don't have the permission to do this"))
}
2022-07-13 00:50:32 +08:00
func CheckAccessPermission(userId string, application *Application) (bool, error) {
permissions := GetPermissions(application.Organization)
2022-07-13 00:50:32 +08:00
allowed := true
var err error
for _, permission := range permissions {
if !permission.IsEnabled || len(permission.Users) == 0 {
2022-07-13 00:50:32 +08:00
continue
}
isHit := false
for _, resource := range permission.Resources {
if application.Name == resource {
isHit = true
break
}
}
2022-07-13 00:50:32 +08:00
if isHit {
containsAsterisk := ContainsAsterisk(userId, permission.Users)
if containsAsterisk {
return true, err
}
2022-07-13 00:50:32 +08:00
enforcer := getEnforcer(permission)
allowed, err = enforcer.Enforce(userId, application.Name, "read")
break
}
}
2022-07-13 00:50:32 +08:00
return allowed, err
}
func CheckUsername(username string, lang string) string {
if username == "" {
return i18n.Translate(lang, "check:Empty username.")
} else if len(username) > 39 {
return i18n.Translate(lang, "check:Username is too long (maximum is 39 characters).")
}
exclude, _ := regexp.Compile("^[\u0021-\u007E]+$")
if !exclude.MatchString(username) {
return ""
}
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
re, _ := regexp.Compile("^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$")
if !re.MatchString(username) {
return i18n.Translate(lang, "check:The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.")
}
return ""
}
func CheckToEnableCaptcha(application *Application) bool {
if len(application.Providers) == 0 {
return false
}
for _, providerItem := range application.Providers {
if providerItem.Provider == nil {
continue
}
if providerItem.Provider.Category == "Captcha" && providerItem.Provider.Type == "Default" {
return providerItem.Rule == "Always"
}
}
return false
}