Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add many improvements #52

Merged
merged 12 commits into from
Dec 21, 2023
Merged
2 changes: 1 addition & 1 deletion .lefthook.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ tags = "go,lint"

[pre-commit.commands.test]
glob = "*.go"
run = "go test ./..."
run = "go test ./... -race"
tags = "go,test"

[prepare-commit-msg.commands.gitmoji]
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ dc:org: #dn: dc=org
dc:example: #dn: dc=example,dc=org
ou:group: #dn: ou=group,dc=example,dc=org
cn:owner: &test #dn: cn=admin,ou=group,dc=example,dc=org
objectclass: posixGroup
objectClass: posixGroup
gidNumber: 1000
description: Organization owners
memberUid: [alice]
cn:dev: #dn: cn=dev,ou=group,dc=example,dc=org
objectclass: posixGroup
objectClass: posixGroup
gidNumber: 1001
description: Organization developers
memberUid: [bob, charlie]
cn:qa: #dn: cn=qa,ou=group,dc=example,dc=org
objectclass: posixGroup
objectClass: posixGroup
gidNumber: 1002
memberUid: [charlie, eve]
cn:ok: #dn: cn=ok,ou=group,dc=example,dc=org
Expand All @@ -51,7 +51,7 @@ dc:org: #dn: dc=org
c:global: #dn: c=global,dc=example,dc=org
ou:people: #dn: ou=people,c=global,dc=example,dc=org
cn:alice: #dn: cn=alice,ou=people,c=global,dc=example,dc=org
objectclass: [posixAccount, UserMail]
objectClass: [posixAccount, UserMail]
.#acl:
- !!ldap/acl:allow-on dc=org # allow alice to request everything

Expand All @@ -65,7 +65,7 @@ dc:org: #dn: dc=org
usermail: alice@example.org

cn:bob: #dn: cn=bob,ou=people,c=global,dc=example,dc=org
objectclass: posixAccount
objectClass: posixAccount
.#acl:
- !!ldap/acl:allow-on ou=group,dc=example,dc=org # allow bob request only for user groups

Expand All @@ -78,7 +78,7 @@ dc:org: #dn: dc=org
c:fr: #dn: c=fr,dc=example,dc=org
ou:people: #dn: ou=people,c=fr,dc=example,dc=org
cn:charlie: #dn: cn=charlie,ou=people,c=fr,dc=example,dc=org
objectclass: posixAccount
objectClass: posixAccount
.#acl:
- !!ldap/acl:allow-on ou=group,dc=example,dc=org # allow charlie request for all groups...
- !!ldap/acl:deny-on cn=admin,ou=group,dc=example,dc=org # ...but to owner group
Expand All @@ -92,7 +92,7 @@ dc:org: #dn: dc=org
c:uk: #dn: c=uk,dc=example,dc=org
ou:people: #dn: ou=people,c=fr,dc=example,dc=org
cn:eve: #dn: cn=eve,ou=people,c=uk,dc=example,dc=org
objectclass: posixAccount
objectClass: posixAccount
#NOTE: eve can't make any LDAP request (no !!ldap/bind:password field)
uid: eve
homeDirectory: /home/eve
Expand Down
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ module github.com/xunleii/yaldap

go 1.21

// TODO: remove this once the following issue is resolved: https://github.com/jimlambrt/gldap/pull/58
replace github.com/jimlambrt/gldap => github.com/xunleii/gldap v0.1.10

require (
github.com/alecthomas/kong v0.8.1
github.com/go-asn1-ber/asn1-ber v1.5.5
Expand All @@ -13,6 +16,7 @@ require (
github.com/puzpuzpuz/xsync/v3 v3.0.2
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611
golang.org/x/sync v0.1.0
gopkg.in/yaml.v3 v3.0.1
)

Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/xunleii/gldap v0.1.10 h1:/z/BGhttRhPgsNnYdxW/zaxsA1Rz71e8vtyXa1XZiG4=
github.com/xunleii/gldap v0.1.10/go.mod h1:wQXacI2If7+C8z/IaTIf6Sbb+tqgFoqzujN2AaGzyck=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
Expand All @@ -75,6 +77,7 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
65 changes: 49 additions & 16 deletions internal/ldap/auth/auth.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package auth

import (
"context"
"fmt"
"sync"
"time"

xsync "github.com/puzpuzpuz/xsync/v3"
Expand All @@ -13,15 +15,17 @@ type (
// allowed to perform operations.
Sessions struct {
reg *xsync.MapOf[int, *Session]
ttl time.Duration
}

// Session represents a single LDAP authenticated connection.
Session struct {
refreshable bool
ttl time.Duration

expireAt time.Time
obj ldap.Object

sync sync.RWMutex
}

// SessionOption customize a authnConn before its registration on authConns.
Expand All @@ -31,23 +35,39 @@ type (
Error struct{ error }
)

const defaultTTL = 5 * time.Minute

// NewSessions returns a new AuthnConns instance.
func NewSessions() *Sessions {
return &Sessions{
func NewSessions(ctx context.Context, ttl time.Duration) *Sessions {
sessions := &Sessions{
reg: xsync.NewMapOf[int, *Session](),
ttl: ttl,
}

// Run the GC every TTL/2
go func() {
ticker := time.NewTicker(ttl / 2)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return
case <-ticker.C:
sessions.GC()
}
}
}()

return sessions
}

// NewSession adds the given LDAP object the list of authenticated connections.
func (sessions *Sessions) NewSession(id int, obj ldap.Object, opts ...SessionOption) error {
session := &Session{ttl: defaultTTL, obj: obj}
session := &Session{obj: obj}
for _, opt := range opts {
opt(session)
}

session.expireAt = time.Now().Add(session.ttl)
session.expireAt = time.Now().Add(sessions.ttl)

eobj := sessions.Session(id)
if eobj != nil {
Expand All @@ -58,6 +78,10 @@ func (sessions *Sessions) NewSession(id int, obj ldap.Object, opts ...SessionOpt
return nil
}

// Delete removes the given connection ID from the list of authenticated
// connections.
func (sessions *Sessions) Delete(id int) { sessions.reg.Delete(id) }

// Session returns the LDAP object if it is authenticated. Otherwise, if the
// connection ID doesn't exist or as expired, it returns nil.
// Furthermore, if the connection is expired, it is automatically removed
Expand All @@ -72,24 +96,33 @@ func (sessions *Sessions) Session(id int) *Session {
sessions.reg.Delete(id)
return nil
case session.refreshable:
session.expireAt = time.Now().Add(session.ttl)
session.sync.Lock()
session.expireAt = time.Now().Add(sessions.ttl)
session.sync.Unlock()
}

return session
}

// GC removes all expired connections from the list of authenticated.
func (sessions Sessions) GC() {
sessions.reg.Range(func(key int, value *Session) bool {
value.sync.RLock()
defer value.sync.RUnlock()
if value.expireAt.Before(time.Now()) {
sessions.reg.Delete(key)
}
return true
})
}

// Object returns the LDAP object associated with the given session.
func (session Session) Object() ldap.Object {
func (session *Session) Object() ldap.Object {
return session.obj
}

// AuthnRefreshable allows the given conn to have its expiration date increased
// WithRefreshable allows the given conn to have its expiration date increased
// after each operation.
func AuthnRefreshable() SessionOption {
func WithRefreshable() SessionOption {
return func(session *Session) { session.refreshable = true }
}

// AuthnTTL customizes the given conn TTL.
func AuthnTTL(ttl time.Duration) SessionOption {
return func(session *Session) { session.ttl = ttl }
}
Loading
Loading