Compare commits

..

1 Commits

  1. 16
      Makefile
  2. 7
      README.md
  3. 72
      db/collective.go
  4. 85
      db/collective_test.go
  5. 2
      db/db.go
  6. 5
      db/invite_test.go
  7. 35
      db/openpgp.go
  8. 68
      db/openpgp_test.go
  9. 8
      examples/data.ldif
  10. 6
      examples/lastbind-module-enable.ldif
  11. 6
      examples/lastbind-module-load.ldif
  12. 2
      examples/lowry.conf
  13. 8
      examples/passwd-format.ldif
  14. 5
      examples/sample-users-first-login.sh
  15. 9
      examples/sindominio.ldif
  16. 13
      examples/sindominio.schema
  17. 11
      gitea/gitea.go
  18. 7
      go.mod
  19. 53
      go.sum
  20. 8
      ldap/errors.go
  21. 80
      ldap/group.go
  22. 4
      ldap/group_test.go
  23. 2
      ldap/ldap_test.go
  24. 101
      ldap/openpgp.go
  25. 143
      ldap/openpgp_test.go
  26. 53
      ldap/user.go
  27. 16
      ldap/user_test.go
  28. 1
      mail/mail.go
  29. 88
      main.go
  30. 38
      server/add_user.go
  31. 124
      server/admin.go
  32. 123
      server/cleanup.go
  33. 250
      server/collective.go
  34. 40
      server/gitea.go
  35. 181
      server/openpgp.go
  36. 63
      server/server.go
  37. 17
      server/template.go
  38. 10
      server/user.go
  39. 3
      tmpl/403.html
  40. 3
      tmpl/404.html
  41. 3
      tmpl/500.html
  42. 30
      tmpl/add_collective.html
  43. 85
      tmpl/collective.html
  44. 34
      tmpl/collectives.html
  45. 22
      tmpl/error.html
  46. 6
      tmpl/gitea.html
  47. 64
      tmpl/group.html
  48. 48
      tmpl/groups.html
  49. 5
      tmpl/index.html
  50. 11
      tmpl/navbar.html
  51. 44
      tmpl/openpgp.html
  52. 14
      tmpl/openpgp_expire.mail
  53. 6
      tmpl/user.html
  54. 57
      tmpl/wellcome.mail

16
Makefile

@ -17,15 +17,19 @@ clean:
rm -rf node_modules dist lowry
deps:
sudo apt install ldap-utils golang npm
sudo apt install slapd ldap-utils golang npm
run_ldap:
docker run --rm -d -p 389:3389 --name ldap registry.sindominio.net/ldap
sleep 1
ldapadd -x -w password -H ldap://localhost:389/ -f examples/data.ldif -D "cn=admin,dc=nodomain"
fixtures:
sudo cp examples/sindominio.* /etc/ldap/schema/
sudo ldapadd -Y EXTERNAL -H ldapi:// -f /etc/ldap/schema/sindominio.ldif
sudo ldapmodify -Y EXTERNAL -H ldapi:// -f examples/passwd-format.ldif
sudo ldapmodify -Y EXTERNAL -H ldapi:// -f examples/lastbind-module-enable.ldif
sudo ldapadd -Y EXTERNAL -H ldapi:// -f examples/lastbind-module-load.ldif
sudo slapadd -n 1 -l examples/data.ldif
sudo $(shell examples/sample-users-first-login.sh)
demo:
./lowry -noLockUsers -config examples/lowry.conf
./lowry -config examples/lowry.conf
test:
go test ./...

7
README.md

@ -1,14 +1,9 @@
Set up a testing environment:
```
make deps
make fixtures
```
Launch ldap in a docker container:
```
make run_ldap
```
For more info on how to keep persistant storage for the ldap server check the [docker-compose.yml](https://git.sindominio.net/estibadores/ldap/src/branch/master/docker-compose.yml).
Build lowry and run it with the demo data:
```
make all

72
db/collective.go

@ -1,72 +0,0 @@
package db
import (
"encoding/json"
"errors"
"log"
"time"
"go.etcd.io/bbolt"
)
var (
collectiveBucket = []byte("collective")
)
type collective struct {
Name string
CreationDate time.Time
}
// AddCollective stores in the db the collective that was created by host
func (db *DB) AddCollective(name string, host string) error {
var collectives []collective
err := db.get(collectiveBucket, host, &collectives)
if err != nil && !errors.Is(err, notFoundError{}) {
return err
}
collectives = append(collectives, collective{name, time.Now()})
return db.put(collectiveBucket, host, collectives)
}
// CountCollectives returns the nubmer of collectives created by host
func (db *DB) CountCollectives(host string) (int, error) {
var collectives []collective
err := db.get(collectiveBucket, host, &collectives)
if errors.Is(err, notFoundError{}) {
return 0, nil
}
return len(collectives), err
}
// ExpireCollectives older than duration
func (db *DB) ExpireCollectives(duration time.Duration) error {
return db.bolt.Update(func(tx *bbolt.Tx) error {
b := tx.Bucket(collectiveBucket)
return b.ForEach(func(k, v []byte) error {
var collectives []collective
err := json.Unmarshal(v, &collectives)
if err != nil {
log.Printf("Error unmarshalling %s: %v", string(k), err)
return nil
}
newCollectives := []collective{}
for _, c := range collectives {
if c.CreationDate.Add(duration).After(time.Now()) {
newCollectives = append(newCollectives, c)
}
}
if len(newCollectives) == len(collectives) {
return nil
}
encodedValue, err := json.Marshal(newCollectives)
if err != nil {
return err
}
return b.Put(k, encodedValue)
})
})
}

85
db/collective_test.go

@ -1,85 +0,0 @@
package db
import (
"testing"
"time"
)
const (
collectiveName = "collective"
collectiveHost = "host"
)
func TestAddCountCollective(t *testing.T) {
db := initTestDB(t)
defer delTestDB(db)
count, err := db.CountCollectives(collectiveHost)
if err != nil {
t.Fatalf("Got an error counting collectives: %v", err)
}
if count != 0 {
t.Errorf("Got an unexpected number of collectives: %d", count)
}
err = db.AddCollective(collectiveName, collectiveHost)
if err != nil {
t.Fatalf("Got an error adding a collective: %v", err)
}
count, err = db.CountCollectives(collectiveHost)
if err != nil {
t.Fatalf("Got an error counting collectives: %v", err)
}
if count != 1 {
t.Errorf("Got an unexpected number of collectives: %d", count)
}
err = db.AddCollective(collectiveName+"1", collectiveHost)
if err != nil {
t.Fatalf("Got an error adding a collective: %v", err)
}
err = db.AddCollective(collectiveName+"2", collectiveHost)
if err != nil {
t.Fatalf("Got an error adding a collective: %v", err)
}
count, err = db.CountCollectives(collectiveHost)
if err != nil {
t.Fatalf("Got an error counting collectives: %v", err)
}
if count != 3 {
t.Errorf("Got an unexpected number of collectives: %d", count)
}
}
func TestExpireCollectives(t *testing.T) {
db := initTestDB(t)
defer delTestDB(db)
err := db.AddCollective(collectiveName, collectiveHost)
if err != nil {
t.Fatalf("Got an error adding a collective: %v", err)
}
count, err := db.CountCollectives(collectiveHost)
if err != nil {
t.Fatalf("Got an error counting collectives: %v", err)
}
if count != 1 {
t.Errorf("Got an unexpected number of collectives: %d", count)
}
err = db.ExpireCollectives(time.Microsecond)
if err != nil {
t.Fatalf("Got an error expiring invites: %v", err)
}
count, err = db.CountCollectives(collectiveHost)
if err != nil {
t.Fatalf("Got an error counting collectives: %v", err)
}
if count != 0 {
t.Errorf("Got an unexpected number of collectives: %d", count)
}
}

2
db/db.go

@ -21,7 +21,7 @@ func Init(path string) (*DB, error) {
}
err = bolt.Update(func(tx *bbolt.Tx) error {
for _, bucket := range [][]byte{inviteBucket, accountBucket, collectiveBucket, openpgpNotificationsBucket} {
for _, bucket := range [][]byte{inviteBucket, accountBucket} {
_, err := tx.CreateBucketIfNotExists(bucket)
if err != nil {

5
db/invite_test.go

@ -67,10 +67,7 @@ func TestListUserInvites(t *testing.T) {
if err != nil {
t.Fatalf("Got an error listing invites: %v", err)
}
if len(invites) != 1 {
t.Fatalf("Unexpected number of invites: %v", invites)
}
if invites[0].Code != code {
if len(invites) != 1 && invites[0] != code {
t.Fatalf("Code not in list of user invites: %v", invites)
}
}

35
db/openpgp.go

@ -1,35 +0,0 @@
package db
import (
"errors"
"time"
)
var (
openpgpNotificationsBucket = []byte("openpgp-notifications")
)
type openpgpNotification struct {
Fingerprint string
CreationDate time.Time
}
// AddOpenpgpNotification stores in the db the dn being notified for their key being expired
func (db *DB) AddOpenpgpNotification(dn string, fingerprint string) error {
return db.put(openpgpNotificationsBucket, dn, openpgpNotification{fingerprint, time.Now()})
}
// GetOpenpgpNotification gets the fingerprint latest notification for the dn
func (db *DB) GetOpenpgpNotification(dn string) (string, error) {
var notif openpgpNotification
err := db.get(openpgpNotificationsBucket, dn, &notif)
if errors.Is(err, notFoundError{}) {
err = nil
}
return notif.Fingerprint, err
}
// ExpireOpenpgpNotifications older than duration
func (db *DB) ExpireOpenpgpNotifications(duration time.Duration) error {
return db.expire(openpgpNotificationsBucket, duration)
}

68
db/openpgp_test.go

@ -1,68 +0,0 @@
package db
import (
"testing"
"time"
)
const (
user = "user"
fingerprint = "AABBCCDDEEFF1122334455"
)
func TestAddOpenPGPNotification(t *testing.T) {
db := initTestDB(t)
defer delTestDB(db)
fp, err := db.GetOpenpgpNotification(user)
if err != nil {
t.Fatalf("Got an error getting openpgp notification: %v", err)
}
if fp != "" {
t.Errorf("Got an unexpected fingerprint: %s", fp)
}
err = db.AddOpenpgpNotification(user, fingerprint)
if err != nil {
t.Fatalf("Got an error adding a openpgp notification: %v", err)
}
fp, err = db.GetOpenpgpNotification(user)
if err != nil {
t.Fatalf("Got an error getting openpgp notification: %v", err)
}
if fp != fingerprint {
t.Errorf("Got an unexpected fingerprint: %s", fp)
}
}
func TestExpireOpenpgpNofications(t *testing.T) {
db := initTestDB(t)
defer delTestDB(db)
err := db.AddOpenpgpNotification(user, fingerprint)
if err != nil {
t.Fatalf("Got an error adding a openpgp notification: %v", err)
}
fp, err := db.GetOpenpgpNotification(user)
if err != nil {
t.Fatalf("Got an error getting openpgp notification: %v", err)
}
if fp != fingerprint {
t.Errorf("Got an unexpected fingerprint: %s", fp)
}
err = db.ExpireOpenpgpNotifications(time.Microsecond)
if err != nil {
t.Fatalf("Got an error expiring openpgp notifications: %v", err)
}
fp, err = db.GetOpenpgpNotification(user)
if err != nil {
t.Fatalf("Got an error getting openpgp notification: %v", err)
}
if fp != "" {
t.Errorf("Got an unexpected fingerprint: %s", fp)
}
}

8
examples/data.ldif

@ -12,9 +12,7 @@ objectClass: shadowAccount
objectClass: sdPerson
objectClass: top
sdRole: amiga
userPassword:: e0NSWVBUfSQ2JFIvbjJ6WHlKTG1wSUJCclUkWWd4ajBKcE9IdktoWFpEcE11TVp
PQXVIRmp0VTl6ekc1WGZXejI0cElyblpzN3oyeU5hcDFjU0NydEhtS0RLOVhjR2dWN3RuZVpjaHo2
MEdPMkNtWTE=
userPassword: {CRYPT}$6$$p0Hh2EoSEDPBIwNrVnlnl4hs2B8uY76fu87IXlC2CFoPwJlzY8nA1Hv/n5ykGE1oYlTg.LKjtUcbkHkkwA4ny/
loginShell: /bin/bash
uidNumber: 1000
gidNumber: 1000
@ -31,9 +29,7 @@ objectClass: shadowAccount
objectClass: sdPerson
objectClass: top
sdRole: sindominante
userPassword:: e0NSWVBUfSQ2JDhpZ3pEZmgwdWZjVEt1c0okOEtPMUVuM3hrU005M2lrYXJQR0c
xMWRtREdqTUVqRHZaazZCMEs5NEY0aGF6aEJLc1p2MW5sUWI5d052ZDk1d0FaWkxnVkZhYlkzb0wv
VzZZeDJIdzA=
userPassword: {CRYPT}$6$$p0Hh2EoSEDPBIwNrVnlnl4hs2B8uY76fu87IXlC2CFoPwJlzY8nA1Hv/n5ykGE1oYlTg.LKjtUcbkHkkwA4ny/
loginShell: /bin/bash
uidNumber: 1001
gidNumber: 1001

6
examples/lastbind-module-enable.ldif

@ -0,0 +1,6 @@
# Enable module lastbind
# ldapmodify -Y EXTERNAL -H ldapi:///
dn: cn=module{0},cn=config
add: olcModuleLoad
olcModuleLoad: {0}lastbind

6
examples/lastbind-module-load.ldif

@ -0,0 +1,6 @@
# Config slapd to use lastbind overlay:
# ldapadd -Y EXTERNAL -H ldapi:///
dn: olcOverlay={0}lastbind, olcDatabase={1}mdb,cn=config
objectClass: olcLastBindConfig
olcOverlay: {0}lastbind

2
examples/lowry.conf

@ -1,5 +1,5 @@
ldapaddr=localhost:389
ldappass=password
ldappass=foobar
domain=nodomain
httpaddr=:8080
giteaURL=

8
examples/passwd-format.ldif

@ -0,0 +1,8 @@
# ldapmodify -Y EXTERNAL -H ldapi:///
dn: cn=config
add: olcPasswordHash
olcPasswordHash: {CRYPT}
-
add: olcPasswordCryptSaltFormat
olcPasswordCryptSaltFormat: $6$%.16s

5
examples/sample-users-first-login.sh

@ -0,0 +1,5 @@
# Login to ldap so authTimestamp exists before first run.
ldapsearch -D 'uid=user,ou=People,dc=nodomain' -w foobar -b 'uid=user,ou=People,dc=nodomain'
ldapsearch -D 'uid=superuser,ou=People,dc=nodomain' -w foobar -b 'uid=superuser,ou=People,dc=nodomain'
ldapsearch -D 'uid=pebles,ou=People,dc=nodomain' -w foobar -b 'uid=pebles,ou=pebles,dc=nodomain'

9
examples/sindominio.ldif

@ -0,0 +1,9 @@
dn: cn=sindominio,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: sindominio
olcAttributeTypes: ( 1.3.6.1.4.1.42023.11 NAME 'sdRole' DESC 'Rol en SinDominio'
SUP name SINGLE-VALUE )
olcAttributeTypes: ( 1.3.6.1.4.1.42023.12 NAME 'sdLocked'
DESC 'Cuenta de SinDominio bloqueada' SUP name )
olcObjectClasses: ( 1.3.6.1.4.1.42023.10 NAME 'sdPerson'
DESC 'Personas de SinDominio' SUP top AUXILIARY MUST sdRole MAY sdLocked )

13
examples/sindominio.schema

@ -0,0 +1,13 @@
attributetype ( 1.3.6.1.4.1.42023.11 NAME 'sdRole'
DESC 'Rol en SinDominio'
SUP name SINGLE-VALUE)
attributetype ( 1.3.6.1.4.1.42023.12 NAME 'sdLocked'
DESC 'Cuenta de SinDominio bloqueada'
SUP name)
objectclass ( 1.3.6.1.4.1.42023.10 NAME 'sdPerson'
DESC 'Personas de SinDominio'
SUP top AUXILIARY
MUST sdRole
MAY sdLocked)

11
gitea/gitea.go

@ -69,21 +69,16 @@ func (g *Gitea) Repo(user string, repo string) (*Repo, error) {
// UserID check users exists and return user id
func (r *Repo) UserID() (int, error) {
userexist, err := r.client.SearchUsers(r.user, 0)
userexist, err := r.client.SearchUsers(r.user, 1)
if err != nil {
return -1, err
}
uid := -2
emptyuser := []*gitea.User{}
if reflect.DeepEqual(userexist, emptyuser) == true {
return uid, err
return -2, err
}
for i := 0; i < len(userexist); i++ {
if userexist[i].UserName == r.user {
uid = int(userexist[i].ID)
}
}
uid := int(userexist[0].ID)
return uid, nil
}

7
go.mod

@ -4,8 +4,7 @@ go 1.12
require (
code.gitea.io/sdk/gitea v0.11.3
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect
github.com/ProtonMail/gopenpgp/v2 v2.4.6
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.4.1 // indirect
github.com/go-ldap/ldap/v3 v3.1.8
github.com/gorilla/mux v1.7.4
@ -13,8 +12,8 @@ require (
github.com/gorilla/sessions v1.2.0
github.com/kr/pretty v0.1.0 // indirect
github.com/namsral/flag v1.7.4-pre
github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa
go.etcd.io/bbolt v1.3.4
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

53
go.sum

@ -1,16 +1,9 @@
code.gitea.io/sdk/gitea v0.11.3 h1:CdI3J82Mqn0mElyEKa5DUSr3Wi2R+qm/6uVtCkSSqSM=
code.gitea.io/sdk/gitea v0.11.3/go.mod h1:z3uwDV/b9Ls47NGukYM9XhnHtqPh/J+t40lsUrR6JDY=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 h1:cSHEbLj0GZeHM1mWG84qEnGFojNEQ83W7cwaPRjcwXU=
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f h1:CGq7OieOz3wyQJ1fO8S0eO9TCW1JyvLrf8fhzz1i8ko=
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/gopenpgp/v2 v2.4.6 h1:/EcJsFIsE0ywShAJ+lNLafcaSd6GBhIzHsaBID5pGXw=
github.com/ProtonMail/gopenpgp/v2 v2.4.6/go.mod h1:ZW1KxHNG6q5LMgFKf9Ap/d2eVYeyGf5+fAUEAjJWtmo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-asn1-ber/asn1-ber v1.4.1 h1:qP/QDxOtmMoJVgXHCXNzDpA0+wkgYB2x5QoLMVOciyw=
github.com/go-asn1-ber/asn1-ber v1.4.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
@ -24,8 +17,6 @@ github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYb
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -33,57 +24,23 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/namsral/flag v1.7.4-pre h1:b2ScHhoCUkbsq0d2C15Mv+VU8bl8hAXV8arnWiOHNZs=
github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7MsceFUpB5yDo=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa h1:2EwhXkNkeMjX9iFYGWLPQLPhw9O58BhnYgtYKeqybcY=
github.com/tv42/zbase32 v0.0.0-20220222190657-f76a9fc892fa/go.mod h1:is48sjgBanWcA5CQrPBu9Y5yABY/T2awj/zI65bq704=
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

8
ldap/errors.go

@ -1,8 +0,0 @@
package ldap
import (
"errors"
)
var ErrAlreadyExist = errors.New("Already exist an entry with this uid/cn")
var ErrNotFound = errors.New("Can't find the record in the ldap")

80
ldap/group.go

@ -4,19 +4,15 @@ import (
"errors"
"fmt"
"strconv"
"time"
"github.com/go-ldap/ldap/v3"
)
// Group has the ldap data of the group
type Group struct {
DN string
Name string
GID int
Description string
Members []string
OpenPGPkey *OpenPGPkey
Name string
GID int
Members []string
}
// InGroup checks if user is part of group
@ -50,7 +46,7 @@ func (l Ldap) GetGroup(name string) (Group, error) {
return Group{}, err
}
if len(groups) == 0 {
return Group{}, ErrNotFound
return Group{}, errors.New("Can't find group " + name)
}
return groups[0], nil
@ -82,9 +78,9 @@ func (l Ldap) UserGroups(user string) ([]Group, error) {
}
// AddGroup adds the group to ldap
func (l Ldap) AddGroup(group string, description string) error {
func (l Ldap) AddGroup(group string) error {
if _, err := l.GetGroup(group); err == nil {
return ErrAlreadyExist
return errors.New("Group '" + group + "' already exist, can't create it")
}
gid, err := l.getLastID("gidNumber")
@ -104,57 +100,16 @@ func (l Ldap) AddGroup(group string, description string) error {
addRequest.Attribute("cn", []string{ldap.EscapeFilter(group)})
addRequest.Attribute("objectClass", []string{"top", "posixGroup"})
addRequest.Attribute("gidNumber", []string{strconv.Itoa(gid)})
if description != "" {
addRequest.Attribute("description", []string{ldap.EscapeFilter(description)})
}
return conn.Add(addRequest)
}
// UpdateGroupDescription set a new description for the group
func (l Ldap) UpdateGroupDescription(group string, description string) error {
conn, err := l.connect()
if err != nil {
return err
}
defer conn.Close()
modifyRequest := ldap.NewModifyRequest(l.groupDN(group), nil)
if description == "" {
modifyRequest.Replace("description", []string{})
} else {
modifyRequest.Replace("description", []string{ldap.EscapeFilter(description)})
}
return conn.Modify(modifyRequest)
}
// DelGroup removes the group in ldap
func (l Ldap) DelGroup(group string) error {
return l.del(l.groupDN(group))
}
// EmptyGroup removes all the members from the group
func (l Ldap) EmptyGroup(group string) error {
filter := fmt.Sprintf("(&(objectClass=posixGroup)(cn=%s))", ldap.EscapeFilter(group))
groups, err := l.searchGroup(filter)
if err != nil {
return err
}
if len(groups) == 0 {
return ErrNotFound
}
return l.delUsersGroup(groups[0].Members, group)
}
// AddUserGroup add user into the group members
func (l Ldap) AddUserGroup(user string, group string) error {
u, err := l.GetUser(user)
if err != nil {
return err
}
if u.Locked != Unlocked {
return ErrNotFound
}
conn, err := l.connect()
if err != nil {
return err
@ -168,10 +123,6 @@ func (l Ldap) AddUserGroup(user string, group string) error {
// DelUserGroup removes the user from the group members
func (l Ldap) DelUserGroup(user string, group string) error {
return l.delUsersGroup([]string{ldap.EscapeFilter(user)}, group)
}
func (l Ldap) delUsersGroup(users []string, group string) error {
conn, err := l.connect()
if err != nil {
return err
@ -179,7 +130,7 @@ func (l Ldap) delUsersGroup(users []string, group string) error {
defer conn.Close()
modifyRequest := ldap.NewModifyRequest(l.groupDN(group), nil)
modifyRequest.Delete("memberUid", users)
modifyRequest.Delete("memberUid", []string{ldap.EscapeFilter(user)})
return conn.Modify(modifyRequest)
}
@ -199,7 +150,7 @@ func (l Ldap) searchGroup(filter string) ([]Group, error) {
"ou=group,"+l.DC,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
append([]string{"dn", "cn", "memberUid", "gidNumber", "description"}, openPGPAttributes[:]...),
[]string{"cn", "memberUid", "gidNumber"},
nil,
)
sr, err := conn.Search(searchRequest)
@ -216,17 +167,8 @@ func (l Ldap) searchGroup(filter string) ([]Group, error) {
func newGroup(entry *ldap.Entry) Group {
gid, _ := strconv.Atoi(entry.GetAttributeValue("gidNumber"))
return Group{
DN: entry.DN,
Name: entry.GetAttributeValue("cn"),
Members: entry.GetAttributeValues("memberUid"),
Description: entry.GetAttributeValue("description"),
GID: gid,
OpenPGPkey: openPGPkey(entry),
Name: entry.GetAttributeValue("cn"),
Members: entry.GetAttributeValues("memberUid"),
GID: gid,
}
}
// ChangeGroupOpenPGPkey updates or sets a new OpenPGPkey for the group
func (l Ldap) ChangeGroupOpenPGPkey(group string, fingerprint string, expiry time.Time, key []byte, wkdHash string) error {
email := fmt.Sprintf("%s@%s", group, l.Domain)
return l.changeOpenPGPkey(l.groupDN(group), fingerprint, expiry, key, wkdHash, email)
}

4
ldap/group_test.go

@ -106,7 +106,7 @@ func TestAddDelGroups(t *testing.T) {
t.Errorf("group %s allready exist", name)
}
err = l.AddGroup(name, "")
err = l.AddGroup(name)
if err != nil {
t.Fatalf("CreateGroup(\"%s\") failed: %v", name, err)
}
@ -123,7 +123,7 @@ func TestAddDelGroups(t *testing.T) {
func TestAddExistingGroup(t *testing.T) {
l := testLdap(t)
err := l.AddGroup("adm", "")
err := l.AddGroup("adm")
if err == nil {
t.Errorf("Create group 'adm' didn't fail")
}

2
ldap/ldap_test.go

@ -5,7 +5,7 @@ import "testing"
const (
addr = "localhost:389"
domain = "nodomain"
pass = "password"
pass = "foobar"
home = "/home/"
)

101
ldap/openpgp.go

@ -1,101 +0,0 @@
package ldap
import (
"errors"
"strings"
"time"
"github.com/go-ldap/ldap/v3"
)
var openPGPAttributes = []string{"openPGPKey", "openPGPId", "openPGPExpiry", "openPGPKeyHash"}
type OpenPGPkey struct {
Fingerprint string
Expiry time.Time
Key []byte
WkdHash string
}
func (l Ldap) changeOpenPGPkey(dn string, fingerprint string, expiry time.Time, key []byte, wkdHash string, email string) error {
conn, err := l.connect()
if err != nil {
return err
}
defer conn.Close()
modifyRequest := ldap.NewModifyRequest(dn, nil)
_, err = l.getOpenPGPKey(dn, conn)
if err != nil {
if errors.Is(err, ErrNotFound) {
modifyRequest.Add("objectClass", []string{"openPGP"})
} else {
return err
}
}
modifyRequest.Replace("openPGPId", []string{fingerprint})
modifyRequest.Replace("openPGPExpiry", []string{expiry.Format(dateFormat)})
modifyRequest.Replace("openPGPKey", []string{string(key)})
modifyRequest.Replace("openPGPKeyHash", []string{wkdHash})
if email != "" {
modifyRequest.Replace("mail", []string{email})
}
return conn.Modify(modifyRequest)
}
func (l Ldap) DeleteOpenPGPkey(dn string) error {
conn, err := l.connect()
if err != nil {
return err
}
defer conn.Close()
modifyRequest := ldap.NewModifyRequest(dn, nil)
modifyRequest.Delete("objectClass", []string{"openPGP"})
modifyRequest.Delete("openPGPId", []string{})
modifyRequest.Delete("openPGPExpiry", []string{})
modifyRequest.Delete("openPGPKey", []string{})
modifyRequest.Delete("openPGPKeyHash", []string{})
if strings.Contains(strings.ToLower(dn), "ou=group") {
modifyRequest.Delete("mail", []string{})
}
return conn.Modify(modifyRequest)
}
func (l Ldap) getOpenPGPKey(dn string, conn *ldap.Conn) (*OpenPGPkey, error) {
searchRequest := ldap.NewSearchRequest(
dn,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
"((objectClass=openPGP))",
openPGPAttributes,
nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
return nil, err
}
switch len(sr.Entries) {
case 1:
entry := sr.Entries[0]
return openPGPkey(entry), nil
case 0:
return nil, ErrNotFound
default:
return nil, errors.New("More than one user found!!!")
}
}
func openPGPkey(entry *ldap.Entry) *OpenPGPkey {
openPGPexpiry, _ := time.Parse(dateFormat, entry.GetAttributeValue("openPGPExpiry"))
key := OpenPGPkey{
Fingerprint: entry.GetAttributeValue("openPGPId"),
Expiry: openPGPexpiry,
Key: []byte(entry.GetAttributeValue("openPGPKey")),
WkdHash: entry.GetAttributeValue("openPGPKeyHash"),
}
if len(key.Key) == 0 || key.Fingerprint == "" || key.WkdHash == "" {
return nil
}
return &key
}

143
ldap/openpgp_test.go

@ -1,143 +0,0 @@
package ldap
import (
"bytes"
"testing"
"time"
)
const (
fingerprint = "AABBCCDDEEFF1122334455"
wkdHash = "hashhash"
)
var (
key = []byte("openpgpkey")
)
func TestOpenPGPuser(t *testing.T) {
l := testLdap(t)
u, err := l.GetUser(user)
if err != nil {
t.Errorf("GetUser() failed: %v", err)
}
if u.OpenPGPkey != nil {
t.Errorf("user already has a key")
}
dn := l.userDN(user)
err = l.changeOpenPGPkey(dn, fingerprint, time.Time{}, key, wkdHash, "")
if err != nil {
t.Errorf("ChangeOpenPGPkey() failed: %v", err)
}
u, err = l.GetUser(user)
if err != nil {
t.Errorf("GetUser() failed: %v", err)
}
if u.OpenPGPkey == nil {
t.Fatal("user doesn't have a key")
}
if u.OpenPGPkey.Fingerprint != fingerprint {
t.Errorf("fingeprint doesn't match: %s", u.OpenPGPkey.Fingerprint)
}
if !bytes.Equal(u.OpenPGPkey.Key, key) {
t.Errorf("key doesn't match: %s", u.OpenPGPkey.Key)
}
if u.OpenPGPkey.WkdHash != wkdHash {
t.Errorf("wkdHash doesn't match: %s", u.OpenPGPkey.WkdHash)
}
if !u.OpenPGPkey.Expiry.IsZero() {
t.Errorf("expiry is not zero: %v", u.OpenPGPkey.Expiry)
}
err = l.DeleteOpenPGPkey(dn)
if err != nil {
t.Errorf("DeleteOpenPGPkey() failed: %v", err)
}
u, err = l.GetUser(user)
if err != nil {
t.Errorf("GetUser() failed: %v", err)
}
if u.OpenPGPkey != nil {
t.Errorf("user already has a key")
}
}
func TestOpenPGPgroup(t *testing.T) {
l := testLdap(t)
g, err := l.GetGroup(group)
if err != nil {
t.Errorf("GetGRoup() failed: %v", err)
}
if g.OpenPGPkey != nil {
t.Errorf("user already has a key")
}
dn := l.groupDN(group)
err = l.changeOpenPGPkey(dn, fingerprint, time.Time{}, key, wkdHash, group+"@nodomain")
if err != nil {
t.Errorf("ChangeOpenPGPkey() failed: %v", err)
}
g, err = l.GetGroup(group)
if err != nil {
t.Errorf("GetGRoup() failed: %v", err)
}
if g.OpenPGPkey == nil {
t.Fatal("user doesn't have a key")
}
if g.OpenPGPkey.Fingerprint != fingerprint {
t.Errorf("fingeprint doesn't match: %s", g.OpenPGPkey.Fingerprint)
}
if !bytes.Equal(g.OpenPGPkey.Key, key) {
t.Errorf("key doesn't match: %s", g.OpenPGPkey.Key)
}
if g.OpenPGPkey.WkdHash != wkdHash {
t.Errorf("wkdHash doesn't match: %s", g.OpenPGPkey.WkdHash)
}
if !g.OpenPGPkey.Expiry.IsZero() {
t.Errorf("expiry is not zero: %v", g.OpenPGPkey.Expiry)
}
err = l.DeleteOpenPGPkey(dn)
if err != nil {
t.Errorf("DeleteOpenPGPkey() failed: %v", err)
}
g, err = l.GetGroup(group)
if err != nil {
t.Errorf("GetGRoup() failed: %v", err)
}
if g.OpenPGPkey != nil {
t.Errorf("user already has a key")
}
}
func TestUpdateOpenPGP(t *testing.T) {
l := testLdap(t)
u, err := l.GetUser(user)
if err != nil {
t.Errorf("GetUser() failed: %v", err)
}
if u.OpenPGPkey != nil {
t.Errorf("user already has a key")
}
dn := l.userDN(user)
err = l.changeOpenPGPkey(dn, fingerprint, time.Time{}, key, wkdHash, "")
if err != nil {
t.Errorf("ChangeOpenPGPkey() failed: %v", err)
}
err = l.changeOpenPGPkey(dn, fingerprint, time.Time{}, key, wkdHash, "")
if err != nil {
t.Errorf("ChangeOpenPGPkey() failed: %v", err)
}
err = l.DeleteOpenPGPkey(dn)
if err != nil {
t.Errorf("DeleteOpenPGPkey() failed: %v", err)
}
}

53
ldap/user.go

@ -15,21 +15,20 @@ const (
dateFormat = "20060102150405Z"
)
var searchAttributes = append([]string{"dn", "uid", "uidNumber", "gidNumber", "loginShell", "homeDirectory", "mail", "authTimestamp", "sdRole", "sdLocked", "userPassword"}, openPGPAttributes[:]...)
var searchAttributes = []string{"dn", "uid", "uidNumber", "gidNumber", "loginShell", "homeDirectory", "mail", "authTimestamp", "sdRole", "sdLocked", "userPassword"}
//User has the ldap data of the user
type User struct {
DN string
Name string
Shell string
Home string
Mail string
UID int
GID int
Role Role
Locked Locked
OpenPGPkey *OpenPGPkey
LastLogin time.Time
DN string
Name string
Shell string
Home string
Mail string
UID int
GID int
Role Role
Locked Locked
LastLogin time.Time
}
// ValidateUser in the ldap
@ -185,7 +184,7 @@ func (l *Ldap) AddUser(user string, pass string, gid int) error {
entry, err := l.searchUser(user, conn)
if entry != nil {
return ErrAlreadyExist
return errors.New("User name already exist: " + user)
}
uid, err := l.getLastID("uidNumber")
@ -249,11 +248,6 @@ func (l Ldap) ChangeLocked(user string, locked Locked) error {
return conn.Modify(modifyRequest)
}
// ChangeUserOpenPGPkey updates or sets a new OpenPGPkey for the user
func (l Ldap) ChangeUserOpenPGPkey(user string, fingerprint string, expiry time.Time, key []byte, wkdHash string) error {
return l.changeOpenPGPkey(l.userDN(user), fingerprint, expiry, key, wkdHash, "")
}
func (l Ldap) changeUser(user, attribute string, value []string) error {
conn, err := l.connect()
if err != nil {
@ -303,7 +297,7 @@ func (l Ldap) searchUser(user string, conn *ldap.Conn) (entry *ldap.Entry, err e
entry = sr.Entries[0]
return entry, nil
case 0:
return entry, ErrNotFound
return entry, errors.New("No user found")
default:
return entry, errors.New("More than one user found!!!")
}
@ -314,16 +308,15 @@ func newUser(entry *ldap.Entry) User {
gid, _ := strconv.Atoi(entry.GetAttributeValue("gidNumber"))
lastLogin, _ := time.Parse(dateFormat, entry.GetAttributeValue("authTimestamp"))
return User{
DN: entry.DN,
Name: entry.GetAttributeValue("uid"),
Shell: entry.GetAttributeValue("loginShell"),
Home: entry.GetAttributeValue("homeDirectory"),
Mail: entry.GetAttributeValue("mail"),
UID: uid,
GID: gid,
Role: RoleFromString(entry.GetAttributeValue("sdRole")),
Locked: LockedFromString(entry.GetAttributeValue("sdLocked")),
OpenPGPkey: openPGPkey(entry),
LastLogin: lastLogin,
DN: entry.DN,
Name: entry.GetAttributeValue("uid"),
Shell: entry.GetAttributeValue("loginShell"),
Home: entry.GetAttributeValue("homeDirectory"),
Mail: entry.GetAttributeValue("mail"),
UID: uid,
GID: gid,
Role: RoleFromString(entry.GetAttributeValue("sdRole")),
Locked: LockedFromString(entry.GetAttributeValue("sdLocked")),
LastLogin: lastLogin,
}
}

16
ldap/user_test.go

@ -1,8 +1,6 @@
package ldap
import (
"testing"
)
import "testing"
const (
user = "user"
@ -12,7 +10,7 @@ const (
func TestValidate(t *testing.T) {
l := testLdap(t)
_, err := l.ValidateUser(user, userPass)
err := l.ValidateUser(user, userPass)
if err != nil {
t.Errorf("Error on ValidateUser(): %v", err)
}
@ -20,7 +18,7 @@ func TestValidate(t *testing.T) {
func TestValidateFails(t *testing.T) {
l := testLdap(t)
_, err := l.ValidateUser(user, userPass+"bar")
err := l.ValidateUser(user, userPass+"bar")
if err == nil {
t.Errorf("ValidateUser() didn't fail to auth the user")
}
@ -33,7 +31,7 @@ func TestChangePass(t *testing.T) {
t.Errorf("Error on ChangePass(): %v", err)
}
_, err = l.ValidateUser(user, newPass)
err = l.ValidateUser(user, newPass)
if err != nil {
t.Errorf("Error on ValidateUser(): %v", err)
}
@ -52,7 +50,7 @@ func TestChangePassRO(t *testing.T) {
t.Errorf("Error on ChangePass(): %v", err)
}
_, err = l.ValidateUser(user, newPass)
err = l.ValidateUser(user, newPass)
if err == nil {
t.Errorf("ValidateUser() didn't fail to auth the user")
}
@ -65,7 +63,7 @@ func TestChangePassAdmin(t *testing.T) {
t.Fatalf("Error on ChangePassAdmin(): %v", err)
}
_, err = l.ValidateUser(user, newPass)
err = l.ValidateUser(user, newPass)
if err != nil {
t.Errorf("Error on ValidateUser(): %v", err)
}
@ -142,7 +140,7 @@ func TestAddUser(t *testing.T) {
t.Errorf("Error on AddUser(): %v", err)
}
_, err = l.ValidateUser(newUser, newPass)
err = l.ValidateUser(newUser, newPass)
if err != nil {
t.Errorf("Error on ValidateUser(): %v", err)
}

1
mail/mail.go

@ -20,7 +20,6 @@ type Mail struct {
func Init(email, password, smtpaddr, domain string) *Mail {
tmpl := template.Must(template.ParseFiles(
"tmpl/wellcome.mail",
"tmpl/openpgp_expire.mail",
))
hostname := strings.Split(smtpaddr, ":")[0]
username := strings.Split(email, "@")[0]

88
main.go

@ -1,7 +1,12 @@
package main
import (
"bufio"
"log"
"os"
"sort"
"strings"
"time"
"git.sindominio.net/sindominio/lowry/db"
"git.sindominio.net/sindominio/lowry/gitea"
@ -11,6 +16,13 @@ import (
"github.com/namsral/flag"
)
var (
inviteExpireDuration = time.Hour * 24 * 30 // 30 days
accountExpireDuration = time.Hour * 24 * 90 // 90 days
accountBlockDuration = time.Hour * 24 * 6 * 30 // ~ 6 months
accountDeleteDuration = time.Hour * 24 * 365 // ~ 1 year
)
func main() {
var (
ldapaddr = flag.String("ldapaddr", "localhost:389", "LDAP server address and port")
@ -23,7 +35,6 @@ func main() {
httpaddr = flag.String("httpaddr", ":8080", "Web server address and port")
dbpath = flag.String("dbpath", "bolt.db", "The path to store the lowry status database")
ro = flag.Bool("ro", false, "Read-Only mode")
noLockUsers = flag.Bool("noLockUsers", false, "Don't lock users (mostly useful for development)")
giteaURL = flag.String("giteaURL", "", "Gitea server address")
token = flag.String("token", "", "Gitea admin token")
cloneAddr = flag.String("cloneAddr", "", "Template repo address to copy")
@ -49,13 +60,82 @@ func main() {
}
g := gitea.Init(*giteaURL, *token, *cloneAddr, *webhookRepoURL, *webhookRepoSecret, *webhookURL, *webhookSecret)
go updateUsers(l)
ldb, err := db.Init(*dbpath)
if err != nil {
log.Fatal(err)
}
defer ldb.Close()
go cleanInvites(ldb)
log.Fatal(server.Serve(*httpaddr, &l, m, ldb, g))
}
func updateUsers(l ldap.Ldap) {
for {
users, err := l.ListUsers()
if err != nil {
log.Printf("Error listing users for locking: %v", err)
time.Sleep(time.Minute * 61)
continue
}
for _, u := range users {
if u.Shell == "/bin/false" && u.Role == ldap.Sindominante {
err := l.ChangeShell(u.Name, "/bin/bash")
if err != nil {
log.Println("An error ocurred changing shell of '", u.Name, "': ", err)
}
}
newLocked := ldap.Unknown
sinceLastLogin := time.Now().Sub(u.LastLogin)
if u.Locked != ldap.Deleted && sinceLastLogin > accountDeleteDuration {
newLocked = ldap.Deleted
} else if u.Locked != ldap.Blocked && sinceLastLogin > accountBlockDuration && sinceLastLogin < accountDeleteDuration {
newLocked = ldap.Blocked
} else {
continue
}
err = l.ChangeLocked(u.Name, newLocked)
if err != nil {
log.Printf("Error changing locked to %s for user %s: %v", newLocked.String(), u.Name, err)
}
if u.Role == ldap.Sindominante {
err = l.ChangeRole(u.Name, ldap.Amiga)
if err != nil {
log.Printf("Error changing role for blocked user %s: %v", u.Name, err)
}
}
}
time.Sleep(time.Minute * 61)
}
}
func cleanInvites(ldb *db.DB) {
for {
ldb.ExpireInvites(inviteExpireDuration)
ldb.ExpireAccounts(accountExpireDuration)
time.Sleep(time.Minute * 60)
}
}
s := server.New(&l, m, ldb, g, *domain)
go s.Cleanup(*noLockUsers)
log.Fatal(s.Serve(*httpaddr))
func readUserList(listPath string) []string {
f, err := os.Open(listPath)