Compare commits

...

30 Commits

Author SHA1 Message Date
meskio 0fccacdd77
Fix OpenPGP key update 2 months ago
meskio 109afb27b8
Correct the expiration date format 2 months ago
meskio 19d91e33c7
Manage openpgp keys for wkd 2 months ago
mina 08eaf393d6 Actualizar 'tmpl/wellcome.mail' 5 months ago
bita 1d7036649f Update 'tmpl/wellcome.mail' 5 months ago
bita 3362d1e165 Update 'tmpl/wellcome.mail' 5 months ago
jorge 31477710d8 Update 'tmpl/wellcome.mail' 5 months ago
salta 4ddbbfeab7 edición y correcciones ortotipográficas del mail de bienvenida 5 months ago
pebles c1f77c5422
Fix user search on gitea api. 7 months ago
pebles 3f939af80c
Fix error management on user creation. 7 months ago
meskio 43fafbcf6b
Give a meaningful error on user adds to collective 7 months ago
meskio 25a58573b8
Send the deletion to the right url 7 months ago
bita 0a1996d220 changes 7 months ago
bita 0bdeb31c66 change 7 months ago
bita 2a52381dc0 correction 7 months ago
bita 9210dcfc84 correction 7 months ago
bita 5096ae4475 correction 7 months ago
bita dd35c6bdd6 correction 7 months ago
meskio 0f6961df27
Usability improvements for the collective view 8 months ago
meskio 5075e21972
Don't fail if there is no error to add user to group 9 months ago
meskio e72c081ede
Fix the ldap doc and test password 9 months ago
meskio 7926084e8c
Empty groups instead of deleting them 1 year ago
meskio 78abc2c76b
Check that the community name is valid 1 year ago
meskio d4c070c9f9
Fix index links and permitions for unloged users 1 year ago
meskio 86eb48e096
Show the community description on the list 1 year ago
meskio fd12aca4c9
Add communities 1 year ago
meskio 493fbbdd70
Add community counter into the DB 1 year ago
meskio f7cb59d4bd
Use docker image for ldap 1 year ago
meskio 16816ee445
Fix tests 1 year ago
meskio 51079f9416
Add a flag to avoid locking users for development 1 year ago
  1. 12
      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. 2
      examples/lowry.conf
  11. 9
      examples/sindominio.ldif
  12. 13
      examples/sindominio.schema
  13. 11
      gitea/gitea.go
  14. 7
      go.mod
  15. 53
      go.sum
  16. 8
      ldap/errors.go
  17. 80
      ldap/group.go
  18. 4
      ldap/group_test.go
  19. 2
      ldap/ldap_test.go
  20. 101
      ldap/openpgp.go
  21. 143
      ldap/openpgp_test.go
  22. 53
      ldap/user.go
  23. 16
      ldap/user_test.go
  24. 1
      mail/mail.go
  25. 88
      main.go
  26. 38
      server/add_user.go
  27. 124
      server/admin.go
  28. 123
      server/cleanup.go
  29. 250
      server/collective.go
  30. 40
      server/gitea.go
  31. 181
      server/openpgp.go
  32. 63
      server/server.go
  33. 17
      server/template.go
  34. 10
      server/user.go
  35. 3
      tmpl/403.html
  36. 3
      tmpl/404.html
  37. 3
      tmpl/500.html
  38. 30
      tmpl/add_collective.html
  39. 85
      tmpl/collective.html
  40. 34
      tmpl/collectives.html
  41. 22
      tmpl/error.html
  42. 6
      tmpl/gitea.html
  43. 64
      tmpl/group.html
  44. 48
      tmpl/groups.html
  45. 5
      tmpl/index.html
  46. 11
      tmpl/navbar.html
  47. 44
      tmpl/openpgp.html
  48. 14
      tmpl/openpgp_expire.mail
  49. 6
      tmpl/user.html
  50. 57
      tmpl/wellcome.mail

12
Makefile

@ -17,15 +17,15 @@ clean:
rm -rf node_modules dist lowry
deps:
sudo apt install slapd ldap-utils golang npm
sudo apt install ldap-utils golang npm
fixtures:
sudo cp examples/sindominio.* /etc/ldap/schema/
sudo ldapadd -Y EXTERNAL -H ldapi:// -f /etc/ldap/schema/sindominio.ldif
sudo slapadd -n 1 -l examples/data.ldif
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"
demo:
./lowry -config examples/lowry.conf
./lowry -noLockUsers -config examples/lowry.conf
test:
go test ./...

7
README.md

@ -1,9 +1,14 @@
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

@ -0,0 +1,72 @@
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

@ -0,0 +1,85 @@
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} {
for _, bucket := range [][]byte{inviteBucket, accountBucket, collectiveBucket, openpgpNotificationsBucket} {
_, err := tx.CreateBucketIfNotExists(bucket)
if err != nil {

5
db/invite_test.go

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

35
db/openpgp.go

@ -0,0 +1,35 @@
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

@ -0,0 +1,68 @@
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,7 +12,9 @@ objectClass: shadowAccount
objectClass: sdPerson
objectClass: top
sdRole: amiga
userPassword: {SSHA}FHqod3gytvH9MDGhpMV1DKjyU7eO1EDG
userPassword:: e0NSWVBUfSQ2JFIvbjJ6WHlKTG1wSUJCclUkWWd4ajBKcE9IdktoWFpEcE11TVp
PQXVIRmp0VTl6ekc1WGZXejI0cElyblpzN3oyeU5hcDFjU0NydEhtS0RLOVhjR2dWN3RuZVpjaHo2
MEdPMkNtWTE=
loginShell: /bin/bash
uidNumber: 1000
gidNumber: 1000
@ -29,7 +31,9 @@ objectClass: shadowAccount
objectClass: sdPerson
objectClass: top
sdRole: sindominante
userPassword: {SSHA}FHqod3gytvH9MDGhpMV1DKjyU7eO1EDG
userPassword:: e0NSWVBUfSQ2JDhpZ3pEZmgwdWZjVEt1c0okOEtPMUVuM3hrU005M2lrYXJQR0c
xMWRtREdqTUVqRHZaazZCMEs5NEY0aGF6aEJLc1p2MW5sUWI5d052ZDk1d0FaWkxnVkZhYlkzb0wv
VzZZeDJIdzA=
loginShell: /bin/bash
uidNumber: 1001
gidNumber: 1001

2
examples/lowry.conf

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

9
examples/sindominio.ldif

@ -1,9 +0,0 @@
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

@ -1,13 +0,0 @@
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,16 +69,21 @@ 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, 1)
userexist, err := r.client.SearchUsers(r.user, 0)
if err != nil {
return -1, err
}
uid := -2
emptyuser := []*gitea.User{}
if reflect.DeepEqual(userexist, emptyuser) == true {
return -2, err
return uid, err
}
uid := int(userexist[0].ID)
for i := 0; i < len(userexist); i++ {
if userexist[i].UserName == r.user {
uid = int(userexist[i].ID)
}
}
return uid, nil
}

7
go.mod

@ -4,7 +4,8 @@ go 1.12
require (
code.gitea.io/sdk/gitea v0.11.3
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220407094043-a94812496cf5 // indirect
github.com/ProtonMail/gopenpgp/v2 v2.4.6
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
@ -12,8 +13,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-20200429183012-4b2356b1ed79
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)

53
go.sum

@ -1,9 +1,16 @@
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=
@ -17,6 +24,8 @@ 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=
@ -24,23 +33,57 @@ 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-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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/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-20200501145240-bc7a7d42d5c3 h1:5B6i6EAiSYyejWfvc5Rc9BbI3rzIsrrXfAQBWnYfn+w=
golang.org/x/sys v0.0.0-20200501145240-bc7a7d42d5c3/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/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

@ -0,0 +1,8 @@
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,15 +4,19 @@ import (
"errors"
"fmt"
"strconv"
"time"
"github.com/go-ldap/ldap/v3"
)
// Group has the ldap data of the group
type Group struct {
Name string
GID int
Members []string
DN string
Name string
GID int
Description string
Members []string
OpenPGPkey *OpenPGPkey
}
// InGroup checks if user is part of group
@ -46,7 +50,7 @@ func (l Ldap) GetGroup(name string) (Group, error) {
return Group{}, err
}
if len(groups) == 0 {
return Group{}, errors.New("Can't find group " + name)
return Group{}, ErrNotFound
}
return groups[0], nil
@ -78,9 +82,9 @@ func (l Ldap) UserGroups(user string) ([]Group, error) {
}
// AddGroup adds the group to ldap
func (l Ldap) AddGroup(group string) error {
func (l Ldap) AddGroup(group string, description string) error {
if _, err := l.GetGroup(group); err == nil {
return errors.New("Group '" + group + "' already exist, can't create it")
return ErrAlreadyExist
}
gid, err := l.getLastID("gidNumber")
@ -100,16 +104,57 @@ func (l Ldap) AddGroup(group 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
@ -123,6 +168,10 @@ 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
@ -130,7 +179,7 @@ func (l Ldap) DelUserGroup(user string, group string) error {
defer conn.Close()
modifyRequest := ldap.NewModifyRequest(l.groupDN(group), nil)
modifyRequest.Delete("memberUid", []string{ldap.EscapeFilter(user)})
modifyRequest.Delete("memberUid", users)
return conn.Modify(modifyRequest)
}
@ -150,7 +199,7 @@ func (l Ldap) searchGroup(filter string) ([]Group, error) {
"ou=group,"+l.DC,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
[]string{"cn", "memberUid", "gidNumber"},
append([]string{"dn", "cn", "memberUid", "gidNumber", "description"}, openPGPAttributes[:]...),
nil,
)
sr, err := conn.Search(searchRequest)
@ -167,8 +216,17 @@ func (l Ldap) searchGroup(filter string) ([]Group, error) {
func newGroup(entry *ldap.Entry) Group {
gid, _ := strconv.Atoi(entry.GetAttributeValue("gidNumber"))
return Group{
Name: entry.GetAttributeValue("cn"),
Members: entry.GetAttributeValues("memberUid"),
GID: gid,
DN: entry.DN,
Name: entry.GetAttributeValue("cn"),
Members: entry.GetAttributeValues("memberUid"),
Description: entry.GetAttributeValue("description"),
GID: gid,
OpenPGPkey: openPGPkey(entry),
}
}
// 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 = "foobar"
pass = "password"
home = "/home/"
)

101
ldap/openpgp.go

@ -0,0 +1,101 @@
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

@ -0,0 +1,143 @@
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,20 +15,21 @@ const (
dateFormat = "20060102150405Z"
)
var searchAttributes = []string{"dn", "uid", "uidNumber", "gidNumber", "loginShell", "homeDirectory", "mail", "authTimestamp", "sdRole", "sdLocked", "userPassword"}
var searchAttributes = append([]string{"dn", "uid", "uidNumber", "gidNumber", "loginShell", "homeDirectory", "mail", "authTimestamp", "sdRole", "sdLocked", "userPassword"}, openPGPAttributes[:]...)
//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
LastLogin time.Time
DN string
Name string
Shell string
Home string
Mail string
UID int
GID int
Role Role
Locked Locked
OpenPGPkey *OpenPGPkey
LastLogin time.Time
}
// ValidateUser in the ldap
@ -184,7 +185,7 @@ func (l *Ldap) AddUser(user string, pass string, gid int) error {
entry, err := l.searchUser(user, conn)
if entry != nil {
return errors.New("User name already exist: " + user)
return ErrAlreadyExist
}
uid, err := l.getLastID("uidNumber")
@ -248,6 +249,11 @@ 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 {
@ -297,7 +303,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, errors.New("No user found")
return entry, ErrNotFound
default:
return entry, errors.New("More than one user found!!!")
}
@ -308,15 +314,16 @@ 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")),
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")),
OpenPGPkey: openPGPkey(entry),
LastLogin: lastLogin,
}
}

16
ldap/user_test.go

@ -1,6 +1,8 @@
package ldap
import "testing"
import (
"testing"
)
const (
user = "user"
@ -10,7 +12,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)
}
@ -18,7 +20,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")
}
@ -31,7 +33,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)
}
@ -50,7 +52,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")
}
@ -63,7 +65,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)
}
@ -140,7 +142,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,6 +20,7 @@ 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,12 +1,7 @@
package main
import (
"bufio"
"log"
"os"
"sort"
"strings"
"time"
"git.sindominio.net/sindominio/lowry/db"
"git.sindominio.net/sindominio/lowry/gitea"
@ -16,13 +11,6 @@ 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")
@ -35,6 +23,7 @@ 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")
@ -60,82 +49,13 @@ 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