2022-09-24 21:48:29 +08:00
// Copyright 2022 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.
2023-04-04 20:51:28 +08:00
package ldap
2022-09-24 21:48:29 +08:00
import (
"fmt"
2023-11-21 14:01:27 +08:00
"hash/fnv"
2022-09-24 21:48:29 +08:00
"log"
2024-01-05 09:24:12 +08:00
"strings"
2022-09-24 21:48:29 +08:00
"github.com/casdoor/casdoor/conf"
"github.com/casdoor/casdoor/object"
2023-04-04 20:51:28 +08:00
ldap "github.com/forestmgy/ldapserver"
2022-09-24 21:48:29 +08:00
"github.com/lor00x/goldap/message"
)
func StartLdapServer ( ) {
2023-10-29 23:55:08 +08:00
ldapServerPort := conf . GetConfigString ( "ldapServerPort" )
if ldapServerPort == "" || ldapServerPort == "0" {
return
}
2023-04-04 20:51:28 +08:00
server := ldap . NewServer ( )
routes := ldap . NewRouteMux ( )
2022-09-24 21:48:29 +08:00
routes . Bind ( handleBind )
routes . Search ( handleSearch ) . Label ( " SEARCH****" )
server . Handle ( routes )
2023-10-29 23:55:08 +08:00
err := server . ListenAndServe ( "0.0.0.0:" + ldapServerPort )
2023-04-04 20:51:28 +08:00
if err != nil {
2023-09-25 20:55:02 +08:00
log . Printf ( "StartLdapServer() failed, err = %s" , err . Error ( ) )
2023-04-04 20:51:28 +08:00
}
2022-09-24 21:48:29 +08:00
}
2023-04-04 20:51:28 +08:00
func handleBind ( w ldap . ResponseWriter , m * ldap . Message ) {
2022-09-24 21:48:29 +08:00
r := m . GetBindRequest ( )
2023-04-04 20:51:28 +08:00
res := ldap . NewBindResponse ( ldap . LDAPResultSuccess )
2022-09-24 21:48:29 +08:00
if r . AuthenticationChoice ( ) == "simple" {
2024-01-05 09:24:12 +08:00
bindDN := string ( r . Name ( ) )
bindPassword := string ( r . AuthenticationSimple ( ) )
if bindDN == "" && bindPassword == "" {
res . SetResultCode ( ldap . LDAPResultInappropriateAuthentication )
res . SetDiagnosticMessage ( "Anonymous bind disallowed" )
w . Write ( res )
return
}
bindUsername , bindOrg , err := getNameAndOrgFromDN ( bindDN )
2023-11-19 19:58:07 +08:00
if err != nil {
log . Printf ( "getNameAndOrgFromDN() error: %s" , err . Error ( ) )
2023-04-04 20:51:28 +08:00
res . SetResultCode ( ldap . LDAPResultInvalidDNSyntax )
2023-11-19 19:58:07 +08:00
res . SetDiagnosticMessage ( fmt . Sprintf ( "getNameAndOrgFromDN() error: %s" , err . Error ( ) ) )
2022-09-24 21:48:29 +08:00
w . Write ( res )
return
}
2023-04-04 20:51:28 +08:00
2023-05-06 23:31:46 +08:00
bindUser , err := object . CheckUserPassword ( bindOrg , bindUsername , bindPassword , "en" )
2023-11-19 19:58:07 +08:00
if err != nil {
2022-09-24 21:48:29 +08:00
log . Printf ( "Bind failed User=%s, Pass=%#v, ErrMsg=%s" , string ( r . Name ( ) ) , r . Authentication ( ) , err )
2023-04-04 20:51:28 +08:00
res . SetResultCode ( ldap . LDAPResultInvalidCredentials )
2023-11-19 19:58:07 +08:00
res . SetDiagnosticMessage ( "invalid credentials ErrMsg: " + err . Error ( ) )
2022-09-24 21:48:29 +08:00
w . Write ( res )
return
}
2023-04-04 20:51:28 +08:00
2023-08-19 12:23:15 +08:00
if bindOrg == "built-in" || bindUser . IsGlobalAdmin ( ) {
2022-09-24 21:48:29 +08:00
m . Client . IsGlobalAdmin , m . Client . IsOrgAdmin = true , true
2023-04-04 20:51:28 +08:00
} else if bindUser . IsAdmin {
2022-09-24 21:48:29 +08:00
m . Client . IsOrgAdmin = true
}
2023-04-04 20:51:28 +08:00
2022-09-24 21:48:29 +08:00
m . Client . IsAuthenticated = true
2023-04-04 20:51:28 +08:00
m . Client . UserName = bindUsername
m . Client . OrgName = bindOrg
2022-09-24 21:48:29 +08:00
} else {
2023-04-04 20:51:28 +08:00
res . SetResultCode ( ldap . LDAPResultAuthMethodNotSupported )
2023-11-19 19:58:07 +08:00
res . SetDiagnosticMessage ( "Authentication method not supported, please use Simple Authentication" )
2022-09-24 21:48:29 +08:00
}
w . Write ( res )
}
2023-04-04 20:51:28 +08:00
func handleSearch ( w ldap . ResponseWriter , m * ldap . Message ) {
res := ldap . NewSearchResultDoneResponse ( ldap . LDAPResultSuccess )
2022-09-24 21:48:29 +08:00
if ! m . Client . IsAuthenticated {
2023-04-04 20:51:28 +08:00
res . SetResultCode ( ldap . LDAPResultUnwillingToPerform )
2022-09-24 21:48:29 +08:00
w . Write ( res )
return
}
2023-04-04 20:51:28 +08:00
2022-09-24 21:48:29 +08:00
r := m . GetSearchRequest ( )
2024-01-05 09:24:12 +08:00
// case insensitive match
if strings . EqualFold ( r . FilterString ( ) , "(objectClass=*)" ) {
if len ( r . Attributes ( ) ) == 0 {
w . Write ( res )
return
}
first_attr := string ( r . Attributes ( ) [ 0 ] )
if string ( r . BaseObject ( ) ) == "" {
// handle special search requests
if first_attr == "namingContexts" {
orgs , code := GetFilteredOrganizations ( m )
if code != ldap . LDAPResultSuccess {
res . SetResultCode ( code )
w . Write ( res )
return
}
e := ldap . NewSearchResultEntry ( string ( r . BaseObject ( ) ) )
dnlist := make ( [ ] message . AttributeValue , len ( orgs ) )
for i , org := range orgs {
dnlist [ i ] = message . AttributeValue ( fmt . Sprintf ( "ou=%s" , org . Name ) )
}
e . AddAttribute ( "namingContexts" , dnlist ... )
w . Write ( e )
} else if first_attr == "subschemaSubentry" {
e := ldap . NewSearchResultEntry ( string ( r . BaseObject ( ) ) )
e . AddAttribute ( "subschemaSubentry" , message . AttributeValue ( "cn=Subschema" ) )
w . Write ( e )
}
} else if strings . EqualFold ( first_attr , "objectclasses" ) && string ( r . BaseObject ( ) ) == "cn=Subschema" {
e := ldap . NewSearchResultEntry ( string ( r . BaseObject ( ) ) )
e . AddAttribute ( "objectClasses" , [ ] message . AttributeValue {
"( 1.3.6.1.1.1.2.0 NAME 'posixAccount' DESC 'Abstraction of an account with POSIX attributes' SUP top AUXILIARY MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) )" ,
"( 1.3.6.1.1.1.2.2 NAME 'posixGroup' DESC 'Abstraction of a group of accounts' SUP top STRUCTURAL MUST ( cn $ gidNumber ) MAY ( userPassword $ memberUid $ description ) )" ,
} ... )
w . Write ( e )
}
2022-09-24 21:48:29 +08:00
w . Write ( res )
return
}
2023-04-04 20:51:28 +08:00
2022-09-24 21:48:29 +08:00
// Handle Stop Signal (server stop / client disconnected / Abandoned request....)
select {
case <- m . Done :
log . Print ( "Leaving handleSearch..." )
return
default :
}
2023-04-04 20:51:28 +08:00
2024-01-05 09:24:12 +08:00
objectClass := searchFilterForEquality ( r . Filter ( ) , "objectClass" , "posixAccount" , "posixGroup" )
switch objectClass {
case "posixAccount" :
users , code := GetFilteredUsers ( m )
if code != ldap . LDAPResultSuccess {
res . SetResultCode ( code )
w . Write ( res )
return
}
2023-04-04 20:51:28 +08:00
2024-01-05 09:24:12 +08:00
// log.Printf("Handling posixAccount filter=%s", r.FilterString())
for _ , user := range users {
dn := fmt . Sprintf ( "uid=%s,cn=users,%s" , user . Name , string ( r . BaseObject ( ) ) )
e := ldap . NewSearchResultEntry ( dn )
attrs := r . Attributes ( )
for _ , attr := range attrs {
if string ( attr ) == "*" {
attrs = AdditionalLdapUserAttributes
break
}
}
for _ , attr := range attrs {
if strings . HasSuffix ( string ( attr ) , ";binary" ) {
// unsupported: userCertificate;binary
continue
}
field , ok := ldapUserAttributesMapping . CaseInsensitiveGet ( string ( attr ) )
if ok {
e . AddAttribute ( message . AttributeDescription ( attr ) , field . GetAttributeValues ( user ) ... )
}
2023-11-22 23:52:40 +08:00
}
2024-01-05 09:24:12 +08:00
w . Write ( e )
2023-11-22 23:52:40 +08:00
}
2024-01-05 09:24:12 +08:00
case "posixGroup" :
// log.Printf("Handling posixGroup filter=%s", r.FilterString())
groups , code := GetFilteredGroups ( m )
if code != ldap . LDAPResultSuccess {
res . SetResultCode ( code )
w . Write ( res )
return
}
for _ , group := range groups {
dn := fmt . Sprintf ( "cn=%s,cn=groups,%s" , group . Name , string ( r . BaseObject ( ) ) )
e := ldap . NewSearchResultEntry ( dn )
attrs := r . Attributes ( )
for _ , attr := range attrs {
if string ( attr ) == "*" {
attrs = AdditionalLdapGroupAttributes
break
}
}
for _ , attr := range attrs {
field , ok := ldapGroupAttributesMapping . CaseInsensitiveGet ( string ( attr ) )
if ok {
e . AddAttribute ( message . AttributeDescription ( attr ) , field . GetAttributeValues ( group ) ... )
}
2023-05-12 13:03:43 +08:00
}
2024-01-05 09:24:12 +08:00
w . Write ( e )
2023-04-13 14:12:31 +08:00
}
2024-01-05 09:24:12 +08:00
case "" :
log . Printf ( "Unmatched search request. filter=%s" , r . FilterString ( ) )
2022-09-24 21:48:29 +08:00
}
2024-01-05 09:24:12 +08:00
2022-09-24 21:48:29 +08:00
w . Write ( res )
}
2023-11-21 14:01:27 +08:00
func hash ( s string ) uint32 {
h := fnv . New32a ( )
h . Write ( [ ] byte ( s ) )
return h . Sum32 ( )
}