mirror of
https://github.com/casdoor/casdoor.git
synced 2025-05-23 02:35:49 +08:00
feat: support simple LDAP server (#1155)
* feat:Support simple ldap server * fix:fix review problems * fix:fix review problems
This commit is contained in:
parent
a447d64bf2
commit
07c1e3b836
@ -19,3 +19,4 @@ origin =
|
|||||||
staticBaseUrl = "https://cdn.casbin.org"
|
staticBaseUrl = "https://cdn.casbin.org"
|
||||||
isDemoMode = false
|
isDemoMode = false
|
||||||
batchSize = 100
|
batchSize = 100
|
||||||
|
ldapServerPort = 389
|
||||||
|
131
controllers/ldapserver.go
Normal file
131
controllers/ldapserver.go
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/casdoor/object"
|
||||||
|
"github.com/forestmgy/ldapserver"
|
||||||
|
"github.com/lor00x/goldap/message"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartLdapServer() {
|
||||||
|
server := ldapserver.NewServer()
|
||||||
|
routes := ldapserver.NewRouteMux()
|
||||||
|
|
||||||
|
routes.Bind(handleBind)
|
||||||
|
routes.Search(handleSearch).Label(" SEARCH****")
|
||||||
|
|
||||||
|
server.Handle(routes)
|
||||||
|
|
||||||
|
go server.ListenAndServe("0.0.0.0:" + conf.GetConfigString("ldapServerPort"))
|
||||||
|
|
||||||
|
// When CTRL+C, SIGINT and SIGTERM signal occurs
|
||||||
|
// Then stop server gracefully
|
||||||
|
ch := make(chan os.Signal)
|
||||||
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-ch
|
||||||
|
close(ch)
|
||||||
|
|
||||||
|
server.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleBind(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
||||||
|
r := m.GetBindRequest()
|
||||||
|
res := ldapserver.NewBindResponse(ldapserver.LDAPResultSuccess)
|
||||||
|
|
||||||
|
if r.AuthenticationChoice() == "simple" {
|
||||||
|
bindusername, bindorg, err := object.GetNameAndOrgFromDN(string(r.Name()))
|
||||||
|
if err != "" {
|
||||||
|
log.Printf("Bind failed ,ErrMsg=%s", err)
|
||||||
|
res.SetResultCode(ldapserver.LDAPResultInvalidDNSyntax)
|
||||||
|
res.SetDiagnosticMessage("bind failed ErrMsg: " + err)
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bindpassword := string(r.AuthenticationSimple())
|
||||||
|
binduser, err := object.CheckUserPassword(bindorg, bindusername, bindpassword)
|
||||||
|
if err != "" {
|
||||||
|
log.Printf("Bind failed User=%s, Pass=%#v, ErrMsg=%s", string(r.Name()), r.Authentication(), err)
|
||||||
|
res.SetResultCode(ldapserver.LDAPResultInvalidCredentials)
|
||||||
|
res.SetDiagnosticMessage("invalid credentials ErrMsg: " + err)
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if bindorg == "built-in" {
|
||||||
|
m.Client.IsGlobalAdmin, m.Client.IsOrgAdmin = true, true
|
||||||
|
} else if binduser.IsAdmin {
|
||||||
|
m.Client.IsOrgAdmin = true
|
||||||
|
}
|
||||||
|
m.Client.IsAuthenticated = true
|
||||||
|
m.Client.UserName = bindusername
|
||||||
|
m.Client.OrgName = bindorg
|
||||||
|
} else {
|
||||||
|
res.SetResultCode(ldapserver.LDAPResultAuthMethodNotSupported)
|
||||||
|
res.SetDiagnosticMessage("Authentication method not supported,Please use Simple Authentication")
|
||||||
|
}
|
||||||
|
w.Write(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleSearch(w ldapserver.ResponseWriter, m *ldapserver.Message) {
|
||||||
|
res := ldapserver.NewSearchResultDoneResponse(ldapserver.LDAPResultSuccess)
|
||||||
|
if !m.Client.IsAuthenticated {
|
||||||
|
res.SetResultCode(ldapserver.LDAPResultUnwillingToPerform)
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r := m.GetSearchRequest()
|
||||||
|
if r.FilterString() == "(objectClass=*)" {
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
name, org, errCode := object.GetUserNameAndOrgFromBaseDnAndFilter(string(r.BaseObject()), r.FilterString())
|
||||||
|
if errCode != ldapserver.LDAPResultSuccess {
|
||||||
|
res.SetResultCode(errCode)
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Handle Stop Signal (server stop / client disconnected / Abandoned request....)
|
||||||
|
select {
|
||||||
|
case <-m.Done:
|
||||||
|
log.Print("Leaving handleSearch...")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
users, errCode := object.GetFilteredUsers(m, name, org)
|
||||||
|
if errCode != ldapserver.LDAPResultSuccess {
|
||||||
|
res.SetResultCode(errCode)
|
||||||
|
w.Write(res)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < len(users); i++ {
|
||||||
|
user := users[i]
|
||||||
|
dn := fmt.Sprintf("cn=%s,%s", user.DisplayName, string(r.BaseObject()))
|
||||||
|
e := ldapserver.NewSearchResultEntry(dn)
|
||||||
|
e.AddAttribute("cn", message.AttributeValue(user.Name))
|
||||||
|
e.AddAttribute("uid", message.AttributeValue(user.Name))
|
||||||
|
e.AddAttribute("email", message.AttributeValue(user.Email))
|
||||||
|
e.AddAttribute("mobile", message.AttributeValue(user.Phone))
|
||||||
|
// e.AddAttribute("postalAddress", message.AttributeValue(user.Address[0]))
|
||||||
|
w.Write(e)
|
||||||
|
}
|
||||||
|
w.Write(res)
|
||||||
|
}
|
2
go.mod
2
go.mod
@ -16,6 +16,7 @@ require (
|
|||||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc
|
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc
|
||||||
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
|
github.com/duo-labs/webauthn v0.0.0-20211221191814-a22482edaa3b
|
||||||
|
github.com/forestmgy/ldapserver v1.0.9
|
||||||
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
|
||||||
github.com/go-ldap/ldap/v3 v3.3.0
|
github.com/go-ldap/ldap/v3 v3.3.0
|
||||||
github.com/go-pay/gopay v1.5.72
|
github.com/go-pay/gopay v1.5.72
|
||||||
@ -26,6 +27,7 @@ require (
|
|||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||||
github.com/lestrrat-go/jwx v0.9.0
|
github.com/lestrrat-go/jwx v0.9.0
|
||||||
github.com/lib/pq v1.8.0
|
github.com/lib/pq v1.8.0
|
||||||
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3
|
||||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
|
||||||
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
github.com/qiangmzsx/string-adapter/v2 v2.1.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
|
4
go.sum
4
go.sum
@ -137,6 +137,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/forestmgy/ldapserver v1.0.9 h1:eMJKsN7Q/c9u074FVXfpPuTp3vOyH+O7Zpe6KfCCMUc=
|
||||||
|
github.com/forestmgy/ldapserver v1.0.9/go.mod h1:1RZ8lox1QSY7rmbjdmy+sYQXY4Lp7SpGzpdE3+j3IyM=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
|
||||||
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
@ -300,6 +302,8 @@ github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|||||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
||||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3 h1:wIONC+HMNRqmWBjuMxhatuSzHaljStc4gjDeKycxy0A=
|
||||||
|
github.com/lor00x/goldap v0.0.0-20180618054307-a546dffdd1a3/go.mod h1:37YR9jabpiIxsb8X9VCIx8qFOjTDIIrIHHODa8C4gz0=
|
||||||
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
|
github.com/markbates/going v1.0.0 h1:DQw0ZP7NbNlFGcKbcE/IVSOAFzScxRtLpd0rLMzLhq0=
|
||||||
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
github.com/markbates/going v1.0.0/go.mod h1:I6mnB4BPnEeqo85ynXIx1ZFLLbtiLHNXVgWeFO9OGOA=
|
||||||
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
|
github.com/mattermost/xml-roundtrip-validator v0.0.0-20201208211235-fe770d50d911 h1:erppMjjp69Rertg1zlgRbLJH1u+eCmRPxKjMZ5I8/Ro=
|
||||||
|
4
main.go
4
main.go
@ -23,6 +23,7 @@ import (
|
|||||||
_ "github.com/astaxie/beego/session/redis"
|
_ "github.com/astaxie/beego/session/redis"
|
||||||
"github.com/casdoor/casdoor/authz"
|
"github.com/casdoor/casdoor/authz"
|
||||||
"github.com/casdoor/casdoor/conf"
|
"github.com/casdoor/casdoor/conf"
|
||||||
|
"github.com/casdoor/casdoor/controllers"
|
||||||
"github.com/casdoor/casdoor/object"
|
"github.com/casdoor/casdoor/object"
|
||||||
"github.com/casdoor/casdoor/proxy"
|
"github.com/casdoor/casdoor/proxy"
|
||||||
"github.com/casdoor/casdoor/routers"
|
"github.com/casdoor/casdoor/routers"
|
||||||
@ -76,5 +77,8 @@ func main() {
|
|||||||
port := beego.AppConfig.DefaultInt("httpport", 8000)
|
port := beego.AppConfig.DefaultInt("httpport", 8000)
|
||||||
// logs.SetLevel(logs.LevelInformational)
|
// logs.SetLevel(logs.LevelInformational)
|
||||||
logs.SetLogFuncCall(false)
|
logs.SetLogFuncCall(false)
|
||||||
|
|
||||||
|
go controllers.StartLdapServer()
|
||||||
|
|
||||||
beego.Run(fmt.Sprintf(":%v", port))
|
beego.Run(fmt.Sprintf(":%v", port))
|
||||||
}
|
}
|
||||||
|
74
object/ldapserver.go
Normal file
74
object/ldapserver.go
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
package object
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/forestmgy/ldapserver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetNameAndOrgFromDN(DN string) (string, string, string) {
|
||||||
|
DNValue := strings.Split(DN, ",")
|
||||||
|
if len(DNValue) == 1 || strings.ToLower(DNValue[0])[0] != 'c' || strings.ToLower(DNValue[1])[0] != 'o' {
|
||||||
|
return "", "", "please use correct Admin Name format like cn=xxx,ou=xxx,dc=example,dc=com"
|
||||||
|
}
|
||||||
|
return DNValue[0][3:], DNValue[1][3:], ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserNameAndOrgFromBaseDnAndFilter(baseDN, filter string) (string, string, int) {
|
||||||
|
if !strings.Contains(baseDN, "ou=") || !strings.Contains(filter, "cn=") {
|
||||||
|
return "", "", ldapserver.LDAPResultInvalidDNSyntax
|
||||||
|
}
|
||||||
|
name := getUserNameFromFilter(filter)
|
||||||
|
_, org, _ := GetNameAndOrgFromDN(fmt.Sprintf("cn=%s,", name) + baseDN)
|
||||||
|
errCode := ldapserver.LDAPResultSuccess
|
||||||
|
return name, org, errCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUserNameFromFilter(filter string) string {
|
||||||
|
nameIndex := strings.Index(filter, "cn=")
|
||||||
|
var name string
|
||||||
|
for i := nameIndex + 3; filter[i] != ')'; i++ {
|
||||||
|
name = name + string(filter[i])
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetFilteredUsers(m *ldapserver.Message, name, org string) ([]*User, int) {
|
||||||
|
var filteredUsers []*User
|
||||||
|
if name == "*" && m.Client.IsOrgAdmin { // get all users from organization 'org'
|
||||||
|
if m.Client.OrgName == "built-in" && org == "*" {
|
||||||
|
filteredUsers = GetGlobalUsers()
|
||||||
|
return filteredUsers, ldapserver.LDAPResultSuccess
|
||||||
|
} else if m.Client.OrgName == "built-in" || org == m.Client.OrgName {
|
||||||
|
filteredUsers = GetUsers(org)
|
||||||
|
return filteredUsers, ldapserver.LDAPResultSuccess
|
||||||
|
} else {
|
||||||
|
return nil, ldapserver.LDAPResultInsufficientAccessRights
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hasPermission, err := CheckUserPermission(fmt.Sprintf("%s/%s", m.Client.OrgName, m.Client.UserName), fmt.Sprintf("%s/%s", org, name), org, true)
|
||||||
|
if !hasPermission {
|
||||||
|
log.Printf("ErrMsg = %v", err.Error())
|
||||||
|
return nil, ldapserver.LDAPResultInsufficientAccessRights
|
||||||
|
}
|
||||||
|
user := getUser(org, name)
|
||||||
|
filteredUsers = append(filteredUsers, user)
|
||||||
|
return filteredUsers, ldapserver.LDAPResultSuccess
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user