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"
2022-04-16 15:10:03 +08:00
"strings"
2022-08-17 01:39:53 +08:00
"time"
2022-08-16 00:14:26 +08:00
"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/util"
2021-12-10 22:45:01 +08:00
goldap "github.com/go-ldap/ldap/v3"
2021-05-01 16:50:47 +08:00
)
2022-01-26 19:36:36 +08:00
var (
reWhiteSpace * regexp . Regexp
reFieldWhiteList * regexp . Regexp
)
2021-05-01 16:50:47 +08:00
2022-08-17 01:39:53 +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 ` )
2022-01-26 19:36:36 +08:00
reFieldWhiteList , _ = regexp . Compile ( ` ^[A-Za-z0-9]+$ ` )
2021-05-01 16:50:47 +08:00
}
2021-02-14 01:04:51 +08:00
2022-02-27 14:02:52 +08:00
func CheckUserSignup ( application * Application , organization * Organization , username string , password string , displayName string , firstName string , lastName string , email string , phone string , affiliation string ) string {
2021-06-17 00:49:02 +08:00
if organization == nil {
return "organization does not exist"
}
if application . IsSignupItemVisible ( "Username" ) {
if len ( username ) <= 1 {
return "username must have at least 2 characters"
2022-08-16 00:14:26 +08:00
}
if unicode . IsDigit ( rune ( username [ 0 ] ) ) {
return "username cannot start with a digit"
}
if util . IsEmailValid ( username ) {
return "username cannot be an email address"
}
if reWhiteSpace . MatchString ( username ) {
2021-06-17 00:49:02 +08:00
return "username cannot contain white spaces"
2022-08-16 00:14:26 +08:00
}
if HasUserByField ( organization . Name , "name" , username ) {
2021-06-17 00:49:02 +08:00
return "username already exists"
}
2022-08-20 12:14:08 +08:00
if HasUserByField ( organization . Name , "email" , email ) {
2022-08-16 00:14:26 +08:00
return "email already exists"
}
2022-08-20 12:14:08 +08:00
if HasUserByField ( organization . Name , "phone" , phone ) {
2022-08-16 00:14:26 +08:00
return "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 {
2021-05-16 21:52:50 +08:00
return "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" ) {
2021-11-09 20:28:27 +08:00
if email == "" {
if application . IsSignupItemRequired ( "Email" ) {
return "email cannot be empty"
} else {
return ""
}
}
2021-06-17 00:49:02 +08:00
if HasUserByField ( organization . Name , "email" , email ) {
return "email already exists"
} else if ! util . IsEmailValid ( email ) {
return "email is invalid"
}
}
if application . IsSignupItemVisible ( "Phone" ) {
2021-11-09 20:28:27 +08:00
if phone == "" {
if application . IsSignupItemRequired ( "Phone" ) {
return "phone cannot be empty"
} else {
return ""
}
}
2021-06-17 00:49:02 +08:00
if HasUserByField ( organization . Name , "phone" , phone ) {
return "phone already exists"
} else if organization . PhonePrefix == "86" && ! util . IsPhoneCnValid ( phone ) {
return "phone number is invalid"
}
}
if application . IsSignupItemVisible ( "Display name" ) {
2022-02-28 13:17:05 +08:00
if application . GetSignupItemRule ( "Display name" ) == "First, last" && ( firstName != "" || lastName != "" ) {
2022-02-27 14:02:52 +08:00
if firstName == "" {
return "firstName cannot be blank"
} else if lastName == "" {
return "lastName cannot be blank"
}
} else {
if displayName == "" {
return "displayName cannot be blank"
} else if application . GetSignupItemRule ( "Display name" ) == "Real name" {
if ! isValidRealName ( displayName ) {
return "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 "affiliation cannot be blank"
}
}
return ""
2021-02-11 22:56:08 +08:00
}
2022-08-17 01:39:53 +08:00
func checkSigninErrorTimes ( user * User ) 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 ( "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 ""
}
2021-05-16 21:04:26 +08:00
func CheckPassword ( user * User , password string ) string {
2022-08-17 01:39:53 +08:00
// check the login error times
if msg := checkSigninErrorTimes ( user ) ; msg != "" {
return msg
}
2021-05-16 22:58:30 +08:00
organization := GetOrganizationByUser ( user )
2021-09-04 22:20:47 +08:00
if organization == nil {
return "organization does not exist"
}
2021-11-04 21:08:43 +08:00
credManager := cred . GetCredManager ( organization . PasswordType )
if credManager != nil {
2021-12-22 20:56:22 +08:00
if organization . MasterPassword != "" {
if credManager . IsPasswordCorrect ( password , organization . MasterPassword , "" , organization . PasswordSalt ) {
2022-08-17 01:39:53 +08:00
resetUserSigninErrorTimes ( user )
2021-12-22 20:56:22 +08:00
return ""
}
2021-11-06 21:14:53 +08:00
}
2021-12-22 13:56:32 +08:00
if credManager . IsPasswordCorrect ( password , user . Password , user . PasswordSalt , organization . PasswordSalt ) {
2022-08-17 01:39:53 +08:00
resetUserSigninErrorTimes ( user )
2021-05-03 10:13:32 +08:00
return ""
}
2022-08-17 01:39:53 +08:00
return recordSigninErrorInfo ( user )
2021-05-03 10:13:32 +08:00
} else {
2021-05-05 23:32:21 +08:00
return fmt . Sprintf ( "unsupported password type: %s" , organization . PasswordType )
2021-05-03 10:13:32 +08:00
}
}
2021-12-10 22:45:01 +08:00
func checkLdapUserPassword ( user * User , password 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 , "Error: 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 , "ldap user name or password incorrect"
}
return user , ""
}
2021-08-15 21:57:36 +08:00
func CheckUserPassword ( organization string , username string , password 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 {
2021-05-01 20:23:20 +08:00
return nil , "the user does not exist, please sign up first"
2021-02-11 22:56:08 +08:00
}
2021-05-05 23:32:21 +08:00
if user . IsForbidden {
return nil , "the user is forbidden to sign in, please contact the administrator"
}
2022-03-28 17:19:58 +08:00
if user . Ldap != "" {
2022-08-07 12:26:14 +08:00
// ONLY for ldap users
2022-03-28 17:19:58 +08:00
return checkLdapUserPassword ( user , password )
} else {
msg := CheckPassword ( user , password )
if msg != "" {
return nil , msg
2022-03-12 21:06:38 +08:00
}
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
}
2022-01-26 19:36:36 +08:00
func filterField ( field string ) bool {
return reFieldWhiteList . MatchString ( field )
2022-02-13 23:39:27 +08:00
}
2022-04-16 15:10:03 +08:00
2022-07-31 10:54:41 +08:00
func CheckUserPermission ( requestUserId , userId , userOwner string , strict bool ) ( bool , error ) {
2022-04-16 15:10:03 +08:00
if requestUserId == "" {
return false , fmt . Errorf ( "please login first" )
}
2022-07-31 10:54:41 +08:00
if userId != "" {
targetUser := GetUser ( userId )
if targetUser == nil {
return false , fmt . Errorf ( "the user: %s doesn't exist" , userId )
}
userOwner = targetUser . Owner
2022-04-16 15:10:03 +08:00
}
hasPermission := false
if strings . HasPrefix ( requestUserId , "app/" ) {
hasPermission = true
} else {
requestUser := GetUser ( requestUserId )
if requestUser == nil {
return false , fmt . Errorf ( "session outdated, please login again" )
}
if requestUser . IsGlobalAdmin {
hasPermission = true
} else if requestUserId == userId {
hasPermission = true
2022-07-31 10:54:41 +08:00
} else if userOwner == requestUser . Owner {
2022-04-16 15:10:03 +08:00
if strict {
hasPermission = requestUser . IsAdmin
} else {
hasPermission = true
}
}
}
return hasPermission , fmt . Errorf ( "you don't have the permission to do this" )
2022-07-13 00:34:35 +08:00
}
2022-07-13 00:50:32 +08:00
func CheckAccessPermission ( userId string , application * Application ) ( bool , error ) {
2022-07-13 00:34:35 +08:00
permissions := GetPermissions ( application . Organization )
2022-07-13 00:50:32 +08:00
allowed := true
2022-07-13 00:34:35 +08:00
var err error
for _ , permission := range permissions {
2022-07-24 23:36:55 +08:00
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:34:35 +08:00
}
}
2022-07-13 00:50:32 +08:00
if isHit {
2022-09-02 12:03:13 +08:00
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:34:35 +08:00
}
2022-07-13 00:50:32 +08:00
return allowed , err
}
2022-10-10 19:58:02 +08:00
func CheckUsername ( name string ) string {
if name == "" {
return "Empty username."
} else if len ( name ) > 39 {
return "Username is too long (maximum is 39 characters)."
}
// https://stackoverflow.com/questions/58726546/github-username-convention-using-regex
2022-10-11 19:39:19 +08:00
re , _ := regexp . Compile ( "^[a-zA-Z0-9]+((?:-[a-zA-Z0-9]+)|(?:_[a-zA-Z0-9]+))*$" )
2022-10-10 19:58:02 +08:00
if ! re . MatchString ( name ) {
2022-10-11 19:39:19 +08:00
return "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."
2022-10-10 19:58:02 +08:00
}
return ""
}