Skip to content

Commit ebb1a1e

Browse files
authored
feat: implement LDAP role manager for Casbin
1 parent 56927e7 commit ebb1a1e

File tree

13 files changed

+1612
-2
lines changed

13 files changed

+1612
-2
lines changed

.github/workflows/ci.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Go
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
branches:
9+
- master
10+
11+
jobs:
12+
test:
13+
runs-on: ubuntu-latest
14+
permissions:
15+
contents: read
16+
17+
steps:
18+
- name: Set up Go
19+
uses: actions/setup-go@v4
20+
with:
21+
go-version: '1.23'
22+
23+
- uses: actions/checkout@v2
24+
25+
- name: Run Unit tests
26+
run: go test -v -coverprofile=./profile.cov ./...
27+
28+
semantic-release:
29+
needs: [test]
30+
runs-on: ubuntu-latest
31+
permissions:
32+
contents: write
33+
issues: write
34+
pull-requests: write
35+
steps:
36+
- uses: actions/checkout@v2
37+
38+
- name: Run semantic-release
39+
if: github.repository == 'casbin/ldap-role-manager' && github.event_name == 'push'
40+
run: |
41+
npm install --save-dev semantic-release@17.2.4
42+
npx semantic-release
43+
env:
44+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
# Output of the go coverage tool, specifically when used with LiteIDE
1212
*.out
13+
*.cov
1314

1415
# Dependency directories (remove the comment below to include it)
1516
# vendor/

.releaserc.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"debug": true,
3+
"branches": [
4+
"+([0-9])?(.{+([0-9]),x}).x",
5+
"master",
6+
{
7+
"name": "beta",
8+
"prerelease": true
9+
}
10+
],
11+
"plugins": [
12+
"@semantic-release/commit-analyzer",
13+
"@semantic-release/release-notes-generator",
14+
"@semantic-release/github"
15+
]
16+
}

README.md

Lines changed: 187 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,187 @@
1-
# ldap-role-manager
2-
LDAP role manager for Casbin
1+
# LDAP Role Manager
2+
3+
[![Go Report Card](https://goreportcard.com/badge/github.com/casbin/ldap-role-manager)](https://goreportcard.com/report/github.com/casbin/ldap-role-manager)
4+
[![Go](https://github.com/casbin/ldap-role-manager/actions/workflows/ci.yml/badge.svg)](https://github.com/casbin/ldap-role-manager/actions/workflows/ci.yml)
5+
[![Coverage Status](https://coveralls.io/repos/github/casbin/ldap-role-manager/badge.svg?branch=master)](https://coveralls.io/github/casbin/ldap-role-manager?branch=master)
6+
[![Godoc](https://godoc.org/github.com/casbin/ldap-role-manager?status.svg)](https://godoc.org/github.com/casbin/ldap-role-manager)
7+
[![Release](https://img.shields.io/github/release/casbin/ldap-role-manager.svg)](https://github.com/casbin/ldap-role-manager/releases/latest)
8+
[![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](https://discord.gg/S5UjpzGZjN)
9+
10+
LDAP Role Manager is an LDAP-based role manager for [Casbin](https://github.com/casbin/casbin). With this library, Casbin can load role hierarchies and user-role mappings from LDAP directories like Active Directory, OpenLDAP, etc.
11+
12+
## Installation
13+
14+
```bash
15+
go get github.com/casbin/ldap-role-manager
16+
```
17+
18+
## Simple Example
19+
20+
```go
21+
package main
22+
23+
import (
24+
"fmt"
25+
"log"
26+
27+
"github.com/casbin/casbin/v2"
28+
ldaprolemanager "github.com/casbin/ldap-role-manager"
29+
)
30+
31+
func main() {
32+
// Initialize LDAP role manager
33+
opts := &ldaprolemanager.LDAPOptions{
34+
URL: "ldap://localhost:389",
35+
BaseDN: "dc=example,dc=com",
36+
UserFilter: "(uid=%s)",
37+
GroupFilter: "(member=%s)",
38+
RoleAttr: "cn",
39+
BindDN: "cn=admin,dc=example,dc=com",
40+
BindPassword: "password",
41+
MaxHierarchyLevel: 10,
42+
}
43+
44+
rm, err := ldaprolemanager.NewRoleManager(opts)
45+
if err != nil {
46+
log.Fatalf("Failed to create role manager: %v", err)
47+
}
48+
defer rm.Close()
49+
50+
// Create a new enforcer
51+
e, err := casbin.NewEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv")
52+
if err != nil {
53+
log.Fatalf("Failed to create enforcer: %v", err)
54+
}
55+
56+
// Set the role manager
57+
e.SetRoleManager(rm)
58+
59+
// Load policy
60+
err = e.LoadPolicy()
61+
if err != nil {
62+
log.Fatalf("Failed to load policy: %v", err)
63+
}
64+
65+
// Check permissions
66+
if res, _ := e.Enforce("alice", "data1", "read"); res {
67+
fmt.Println("alice can read data1")
68+
} else {
69+
fmt.Println("alice cannot read data1")
70+
}
71+
}
72+
```
73+
74+
## Configuration Options
75+
76+
The `LDAPOptions` struct supports the following configuration options:
77+
78+
- **URL**: LDAP server URL (e.g., `ldap://localhost:389` or `ldaps://localhost:636`)
79+
- **BaseDN**: Base distinguished name for searching (e.g., `dc=example,dc=com`)
80+
- **UserFilter**: LDAP filter template for finding users (e.g., `(uid=%s)`)
81+
- **GroupFilter**: LDAP filter template for finding groups (e.g., `(member=%s)`)
82+
- **RoleAttr**: Attribute name containing role/group names (default: `cn`)
83+
- **BindDN**: Distinguished name for binding to LDAP (optional)
84+
- **BindPassword**: Password for binding to LDAP (optional)
85+
- **UseTLS**: Enable TLS connection (default: `false`)
86+
- **SkipTLSVerify**: Skip TLS certificate verification (default: `false`)
87+
- **MaxHierarchyLevel**: Maximum depth for role hierarchy traversal (default: `10`)
88+
89+
## LDAP Schema Requirements
90+
91+
This role manager expects the following LDAP schema:
92+
93+
### User Entries
94+
- Users should be identifiable by the `UserFilter` (e.g., `uid` attribute)
95+
- Example: `uid=alice,ou=users,dc=example,dc=com`
96+
97+
### Group Entries
98+
- Groups should contain a `member` attribute listing user DNs
99+
- Groups should have a role name attribute (configured via `RoleAttr`, default is `cn`)
100+
- Example: `cn=admin,ou=groups,dc=example,dc=com`
101+
102+
### Example LDIF
103+
104+
```ldif
105+
# User entry
106+
dn: uid=alice,ou=users,dc=example,dc=com
107+
objectClass: inetOrgPerson
108+
uid: alice
109+
cn: Alice Smith
110+
sn: Smith
111+
112+
# Group entry
113+
dn: cn=admin,ou=groups,dc=example,dc=com
114+
objectClass: groupOfNames
115+
cn: admin
116+
member: uid=alice,ou=users,dc=example,dc=com
117+
```
118+
119+
## Active Directory Example
120+
121+
For Active Directory, you might use different filters:
122+
123+
```go
124+
opts := &ldaprolemanager.LDAPOptions{
125+
URL: "ldaps://ad.example.com:636",
126+
BaseDN: "dc=example,dc=com",
127+
UserFilter: "(sAMAccountName=%s)",
128+
GroupFilter: "(member=%s)",
129+
RoleAttr: "cn",
130+
BindDN: "cn=service-account,ou=users,dc=example,dc=com",
131+
BindPassword: "password",
132+
UseTLS: true,
133+
}
134+
```
135+
136+
## Features
137+
138+
- **Read-only Role Management**: Roles are managed in LDAP, not in Casbin policies
139+
- **Role Hierarchy Support**: Supports nested group memberships up to `MaxHierarchyLevel`
140+
- **Multiple Group Membership**: Users can belong to multiple groups/roles
141+
- **Flexible LDAP Schema**: Configurable filters and attributes to match your LDAP schema
142+
- **TLS Support**: Secure connections to LDAP servers
143+
- **Thread-safe**: Safe for concurrent use
144+
145+
## API
146+
147+
The LDAP Role Manager implements the `rbac.RoleManager` interface:
148+
149+
- `GetRoles(name string, domain ...string) ([]string, error)` - Get roles for a user
150+
- `GetUsers(roleName string, domain ...string) ([]string, error)` - Get users with a specific role
151+
- `HasLink(name1 string, name2 string, domain ...string) (bool, error)` - Check if a user has a role
152+
- `GetImplicitRoles(name string, domain ...string) ([]string, error)` - Get all roles including nested ones
153+
- `GetImplicitUsers(roleName string, domain ...string) ([]string, error)` - Get all users with a role
154+
155+
Note: Methods like `AddLink` and `DeleteLink` are no-ops since roles are managed in LDAP.
156+
157+
## Testing
158+
159+
Run the tests with:
160+
161+
```bash
162+
go test -v ./...
163+
```
164+
165+
For tests with coverage:
166+
167+
```bash
168+
go test -v -coverprofile=coverage.out ./...
169+
go tool cover -html=coverage.out
170+
```
171+
172+
## Related Projects
173+
174+
- [Casbin](https://github.com/casbin/casbin) - Authorization library
175+
- [Gorm Adapter](https://github.com/casbin/gorm-adapter) - Casbin adapter for GORM
176+
- [Redis Watcher](https://github.com/casbin/redis-watcher) - Redis watcher for Casbin
177+
- [Etcd Watcher](https://github.com/casbin/etcd-watcher) - Etcd watcher for Casbin
178+
179+
## Getting Help
180+
181+
- [Casbin Documentation](https://casbin.org/)
182+
- [GitHub Issues](https://github.com/casbin/ldap-role-manager/issues)
183+
- [Discord](https://discord.gg/S5UjpzGZjN)
184+
185+
## License
186+
187+
This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE) file for the full license text.

examples/README.md

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Examples
2+
3+
This directory contains examples demonstrating how to use the LDAP Role Manager with Casbin.
4+
5+
## Files
6+
7+
- **main.go** - A simple example showing how to integrate LDAP role manager with Casbin
8+
- **rbac_model.conf** - Casbin RBAC model configuration
9+
- **rbac_policy.csv** - Sample policies for role-based access control
10+
- **example.ldif** - Sample LDIF file for populating an LDAP directory
11+
12+
## Running the Example
13+
14+
### 1. Set up an LDAP Server
15+
16+
You can use OpenLDAP or any other LDAP server. For testing, you can use Docker:
17+
18+
```bash
19+
docker run -d \
20+
--name openldap \
21+
-p 389:389 \
22+
-e LDAP_ORGANISATION="Example Inc." \
23+
-e LDAP_DOMAIN="example.com" \
24+
-e LDAP_ADMIN_PASSWORD="admin" \
25+
osixia/openldap:latest
26+
```
27+
28+
### 2. Populate the LDAP Directory
29+
30+
Load the example LDIF file into your LDAP server:
31+
32+
```bash
33+
# Using ldapadd
34+
ldapadd -x -D "cn=admin,dc=example,dc=com" -w admin -f example.ldif
35+
36+
# Or using Docker
37+
docker exec openldap ldapadd -x -D "cn=admin,dc=example,dc=com" -w admin -f /tmp/example.ldif
38+
```
39+
40+
You may need to copy the LDIF file into the container first:
41+
42+
```bash
43+
docker cp example.ldif openldap:/tmp/example.ldif
44+
```
45+
46+
### 3. Run the Example
47+
48+
Update the connection details in `main.go` if needed, then run:
49+
50+
```bash
51+
cd examples
52+
go run main.go
53+
```
54+
55+
## Expected Output
56+
57+
The example will check permissions for alice (admin) and bob (user):
58+
59+
```
60+
✓ alice can read data1
61+
✓ alice can write data1
62+
✓ bob can read data1
63+
✗ bob cannot write data1
64+
65+
Roles for alice: [admin user]
66+
Roles for bob: [user]
67+
```
68+
69+
## Verifying LDAP Setup
70+
71+
You can verify your LDAP setup using ldapsearch:
72+
73+
```bash
74+
# Search for all users
75+
ldapsearch -x -D "cn=admin,dc=example,dc=com" -w admin -b "ou=users,dc=example,dc=com"
76+
77+
# Search for all groups
78+
ldapsearch -x -D "cn=admin,dc=example,dc=com" -w admin -b "ou=groups,dc=example,dc=com"
79+
80+
# Search for alice's groups
81+
ldapsearch -x -D "cn=admin,dc=example,dc=com" -w admin -b "dc=example,dc=com" "(member=uid=alice,ou=users,dc=example,dc=com)"
82+
```
83+
84+
## Customization
85+
86+
You can customize the example by:
87+
88+
1. Modifying the LDAP connection parameters in `main.go`
89+
2. Adding more users and groups to `example.ldif`
90+
3. Creating different policies in `rbac_policy.csv`
91+
4. Adjusting the model in `rbac_model.conf`
92+
93+
## Active Directory Example
94+
95+
For Active Directory, you would use different filters and configuration:
96+
97+
```go
98+
opts := &ldaprolemanager.LDAPOptions{
99+
URL: "ldaps://ad.example.com:636",
100+
BaseDN: "dc=example,dc=com",
101+
UserFilter: "(sAMAccountName=%s)",
102+
GroupFilter: "(member=%s)",
103+
RoleAttr: "cn",
104+
BindDN: "cn=service-account,ou=users,dc=example,dc=com",
105+
BindPassword: "password",
106+
UseTLS: true,
107+
}
108+
```

0 commit comments

Comments
 (0)