Manage openpgp keys for wkd

pull/24/head
meskio 8 months ago
parent 08eaf393d6
commit 19d91e33c7
Signed by: meskio
GPG Key ID: 52B8F5AC97A2DA86
  1. 2
      db/db.go
  2. 35
      db/openpgp.go
  3. 68
      db/openpgp_test.go
  4. 7
      go.mod
  5. 53
      go.sum
  6. 13
      ldap/group.go
  7. 70
      ldap/openpgp.go
  8. 115
      ldap/openpgp_test.go
  9. 49
      ldap/user.go
  10. 4
      ldap/user_test.go
  11. 1
      mail/mail.go
  12. 91
      main.go
  13. 8
      server/add_user.go
  14. 12
      server/admin.go
  15. 123
      server/cleanup.go
  16. 48
      server/collective.go
  17. 40
      server/gitea.go
  18. 180
      server/openpgp.go
  19. 44
      server/server.go
  20. 5
      server/template.go
  21. 10
      server/user.go
  22. 9
      tmpl/collective.html
  23. 1
      tmpl/index.html
  24. 44
      tmpl/openpgp.html
  25. 14
      tmpl/openpgp_expire.mail

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

@ -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)
}

@ -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)
}
}

@ -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
)

@ -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=

@ -4,16 +4,19 @@ 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
}
// InGroup checks if user is part of group
@ -196,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", "description"},
append([]string{"dn", "cn", "memberUid", "gidNumber", "description"}, openPGPAttributes[:]...),
nil,
)
sr, err := conn.Search(searchRequest)
@ -213,9 +216,17 @@ 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),
}
}
// 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)
}

@ -0,0 +1,70 @@
package ldap
import (
"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)
modifyRequest.Add("objectClass", []string{"openPGP"})
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 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
}

@ -0,0 +1,115 @@
package ldap
import (
"bytes"
"testing"
"time"
)
func TestOpenPGPuser(t *testing.T) {
key := []byte("openpgpkey")
fingerprint := "AABBCCDDEEFF1122334455"
wkdHash := "hashhash"
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) {
key := []byte("openpgpkey")
fingerprint := "AABBCCDDEEFF1122334455"
wkdHash := "hashhash"
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")
}
}

@ -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
@ -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 {
@ -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,
}
}

@ -1,6 +1,8 @@
package ldap
import "testing"
import (
"testing"
)
const (
user = "user"

@ -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]

@ -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,14 +11,6 @@ import (
"github.com/namsral/flag"
)
var (
inviteExpireDuration = time.Hour * 24 * 30 // 30 days
collectiveExpireDuration = time.Hour * 24 * 90 // 90 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")
@ -62,85 +49,13 @@ func main() {
}
g := gitea.Init(*giteaURL, *token, *cloneAddr, *webhookRepoURL, *webhookRepoSecret, *webhookURL, *webhookSecret)
if !*noLockUsers {
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)
ldb.ExpireCollectives(collectiveExpireDuration)
time.Sleep(time.Minute * 60)
}
}
func readUserList(listPath string) []string {
f, err := os.Open(listPath)
if err != nil {
log.Fatal(err)
}
defer f.Close()
list := []string{}
scanner := bufio.NewScanner(f)
for scanner.Scan() {
user := strings.TrimSpace(scanner.Text())
list = append(list, user)
}
sort.Strings(list)
return list
s := server.New(&l, m, ldb, g, *domain)
go s.Cleanup(*noLockUsers)
log.Fatal(s.Serve(*httpaddr))
}

@ -82,7 +82,7 @@ var (
}
)
func (s *server) listInvitesHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) listInvitesHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("invites", w, r)
invites, err := s.db.ListUserInvites(response.User)
if err != nil {
@ -93,7 +93,7 @@ func (s *server) listInvitesHandler(w http.ResponseWriter, r *http.Request) {
response.execute(invites)
}
func (s *server) deleteInviteHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) deleteInviteHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
invite := vars["invite"]
response := s.newResponse("invites", w, r)
@ -118,7 +118,7 @@ func (s *server) deleteInviteHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, r.Referer(), http.StatusFound)
}
func (s *server) createInviteHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) createInviteHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("invite", w, r)
if response.Role != ldap.Sindominante {
log.Printf("Non sindominante attemp to create an invite, user: %s", response.User)
@ -148,7 +148,7 @@ func (s *server) createInviteHandler(w http.ResponseWriter, r *http.Request) {
}{inviteURL})
}
func (s *server) addUserHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) addUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
invite := vars["invite"]
if !s.db.IsInviteValid(invite) {

@ -9,7 +9,7 @@ import (
"github.com/gorilla/mux"
)
func (s *server) usersHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) usersHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("users", w, r)
if !response.IsAdmin {
log.Println("Non admin attemp to list users")
@ -29,7 +29,7 @@ func (s *server) usersHandler(w http.ResponseWriter, r *http.Request) {
response.execute(users)
}
func (s *server) userHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) userHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userName := vars["name"]
response := s.newResponse("user", w, r)
@ -61,7 +61,7 @@ func (s *server) userHandler(w http.ResponseWriter, r *http.Request) {
response.execute(data)
}
func (s *server) roleHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) roleHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("", w, r)
if !response.IsAdmin {
log.Println("Non admin attemp to change user role")
@ -105,7 +105,7 @@ func (s *server) roleHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/users/"+userName, http.StatusFound)
}
func (s *server) lockHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) lockHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("", w, r)
if !response.IsAdmin {
log.Println("Non admin attemp to change user locked")
@ -132,7 +132,7 @@ func (s *server) lockHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/users/"+userName, http.StatusFound)
}
func (s *server) passwdadmHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) passwdadmHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("", w, r)
if !response.IsAdmin {
log.Println("Non admin attemp to change user password")
@ -152,7 +152,7 @@ func (s *server) passwdadmHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/users/"+userName, http.StatusFound)
}
func (s *server) shellHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) shellHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("", w, r)
if !response.IsAdmin {
log.Println("Non admin attemp to change user password")

@ -0,0 +1,123 @@
package server
import (
"log"
"math"
"time"
"git.sindominio.net/sindominio/lowry/ldap"
)
var (
inviteExpireDuration = time.Hour * 24 * 30 // 30 days
collectiveExpireDuration = time.Hour * 24 * 90 // 90 days
accountExpireDuration = time.Hour * 24 * 90 // 90 days
accountBlockDuration = time.Hour * 24 * 6 * 30 // ~ 6 months
accountDeleteDuration = time.Hour * 24 * 365 // ~ 1 year
notifyKeyExpiredDuration = time.Hour * 24 * 30 // 30 days
)
// Cleanup runs periodic clean up tasks
func (s *Server) Cleanup(noLockUsers bool) {
for {
users, err := s.ldap.ListUsers()
if err != nil {
log.Printf("Error listing users for updating: %v", err)
} else {
for _, u := range users {
if !noLockUsers {
s.updateUserLock(u)
}
s.checkKeyExpiration(u.DN, u.OpenPGPkey, u.Name, u.Mail)
}
}
collectives, err := s.ldap.ListGroups()
if err != nil {
log.Printf("Error listing collectives for updating: %v", err)
} else {
for _, c := range collectives {
mail := c.Name + "@" + s.domain
s.checkKeyExpiration(c.DN, c.OpenPGPkey, c.Name, mail)
}
}
s.expireDBEntries()
time.Sleep(time.Minute * 61)
}
}
func (s *Server) updateUserLock(u ldap.User) {
if u.Shell == "/bin/false" && u.Role == ldap.Sindominante {
err := s.ldap.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 {
return
}
err := s.ldap.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 = s.ldap.ChangeRole(u.Name, ldap.Amiga)
if err != nil {
log.Printf("Error changing role for blocked user %s: %v", u.Name, err)
}
}
}
type NotificationData struct {
Name string
Fingerprint string
Days int
}
func (s *Server) checkKeyExpiration(dn string, key *ldap.OpenPGPkey, name string, mail string) {
if key == nil {
return
}
if key.Expiry.IsZero() {
return
}
if key.Expiry.Before(time.Now()) {
s.ldap.DeleteOpenPGPkey(dn)
}
notifiedFingerprint, err := s.db.GetOpenpgpNotification(dn)
if err != nil {
log.Printf("An error has occurred accessing the user %s last notification: %v", name, err)
}
if key.Expiry.Add(-notifyKeyExpiredDuration).Before(time.Now()) && notifiedFingerprint != key.Fingerprint {
data := NotificationData{
Name: name,
Fingerprint: key.Fingerprint,
Days: int(math.Round(time.Now().Sub(key.Expiry).Hours())),
}
s.mail.Send([]string{mail}, "openpgp_expire", data)
err = s.db.AddOpenpgpNotification(name, key.Fingerprint)
if err != nil {
log.Printf("An error has occurred storing user %s last notification: %v", name, err)
}
}
}
func (s *Server) expireDBEntries() {
s.db.ExpireInvites(inviteExpireDuration)
s.db.ExpireAccounts(accountExpireDuration)
s.db.ExpireCollectives(collectiveExpireDuration)
s.db.ExpireOpenpgpNotifications(notifyKeyExpiredDuration)
}

@ -14,7 +14,7 @@ const (
maxCollectives = 4
)
func (s *server) addCollectiveHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) addCollectiveHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("add_collective", w, r)
ok, err := s.canCreateCollective(response)
if err != nil {
@ -30,7 +30,7 @@ func (s *server) addCollectiveHandler(w http.ResponseWriter, r *http.Request) {
response.execute(status)
}
func (s *server) postAddCollectiveHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) postAddCollectiveHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("add_collective", w, r)
ok, err := s.canCreateCollective(response)
if err != nil {
@ -71,7 +71,7 @@ func (s *server) postAddCollectiveHandler(w http.ResponseWriter, r *http.Request
http.Redirect(w, r, "/collective/"+collectiveName, http.StatusFound)
}
func (s *server) delCollectiveHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) delCollectiveHandler(w http.ResponseWriter, r *http.Request) {
collectiveName := r.FormValue("collective")
response := s.newResponse("", w, r)
@ -90,7 +90,7 @@ func (s *server) delCollectiveHandler(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/collectives/mine/", http.StatusFound)
}
func (s *server) collectivesHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) collectivesHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("collectives", w, r)
if !response.IsAdmin {
log.Println("Non admin attemp to list collectives")
@ -107,7 +107,7 @@ func (s *server) collectivesHandler(w http.ResponseWriter, r *http.Request) {
response.execute(collectives)
}
func (s *server) userCollectivesHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) userCollectivesHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("collectives", w, r)
if response.User == "" {
log.Println("Attemp to list own collective without being logged")
@ -124,7 +124,7 @@ func (s *server) userCollectivesHandler(w http.ResponseWriter, r *http.Request)
response.execute(collectives)
}
func (s *server) collectiveHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) collectiveHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
collectiveName := vars["name"]
response := s.newResponse("collective", w, r)
@ -143,7 +143,7 @@ func (s *server) collectiveHandler(w http.ResponseWriter, r *http.Request) {
response.execute(collective)
}
func (s *server) addUserCollectiveHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) addUserCollectiveHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
collectiveName := vars["name"]
response := s.newResponse("error", w, r)
@ -154,20 +154,13 @@ func (s *server) addUserCollectiveHandler(w http.ResponseWriter, r *http.Request
}
user := r.FormValue("user")
group, err := s.ldap.GetGroup(collectiveName)
isInCollective, err := s.isUserinCollective(user, collectiveName)
if err != nil {
log.Println("An error ocurred getting colelctive ", collectiveName, "to add", user, ":", err)
log.Println("An error ocurred getting collective ", collectiveName, "to add", user, ":", err)
s.errorHandler(w, r)
return
}
found := false
for _, member := range group.Members {
if member == user {
found = true
break
}
}
if found {
if isInCollective {
log.Println("User", user, "already colelctive ", collectiveName, "don't need to add them")
} else {
@ -189,7 +182,7 @@ func (s *server) addUserCollectiveHandler(w http.ResponseWriter, r *http.Request
http.Redirect(w, r, "/collective/"+collectiveName, http.StatusFound)
}
func (s *server) delUserCollectiveHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) delUserCollectiveHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
collectiveName := vars["name"]
response := s.newResponse("", w, r)
@ -209,7 +202,7 @@ func (s *server) delUserCollectiveHandler(w http.ResponseWriter, r *http.Request
http.Redirect(w, r, "/collective/"+collectiveName, http.StatusFound)
}
func (s *server) descriptionCollectiveHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) descriptionCollectiveHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
collectiveName := vars["name"]
response := s.newResponse("", w, r)
@ -229,7 +222,7 @@ func (s *server) descriptionCollectiveHandler(w http.ResponseWriter, r *http.Req
http.Redirect(w, r, "/collective/"+collectiveName, http.StatusFound)
}
func (s *server) canCreateCollective(r responseT) (bool, error) {
func (s *Server) canCreateCollective(r responseT) (bool, error) {
if r.User == "" {
return false, nil
}
@ -240,3 +233,18 @@ func (s *server) canCreateCollective(r responseT) (bool, error) {
count, err := s.db.CountCollectives(r.User)
return count <= maxCollectives, err
}
func (s *Server) isUserinCollective(user, collective string) (bool, error) {
group, err := s.ldap.GetGroup(collective)
if err != nil {
return false, err
}
found := false
for _, member := range group.Members {
if member == user {
found = true
break
}
}
return found, nil
}

@ -6,7 +6,7 @@ import (
"net/http"
)
func (s *server) giteaHandler(w http.ResponseWriter, r *http.Request) {
func (s *Server) giteaHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("gitea", w, r)
// check permission for showing page
@ -23,32 +23,32 @@ func (s *server) giteaHandler(w http.ResponseWriter, r *http.Request) {
return
}
// data for html template
// data for html template
type template_data struct {
ExitStatus string
RepoName string
RepoName string
}
data:= template_data{
data := template_data{
ExitStatus: "",
RepoName: "",
RepoName: "",
}
// init vars
usergitea:= response.User
reponame:= r.FormValue("reponame")
folder:= r.FormValue("folder") // "userfolder" or "usersubfolder"
mode := r.FormValue("mode") // "creterepo" or "setfolder"
usergitea := response.User
reponame := r.FormValue("reponame")
folder := r.FormValue("folder") // "userfolder" or "usersubfolder"
mode := r.FormValue("mode") // "creterepo" or "setfolder"
keypub := ""
keypriv := ""
// check form (TODO: js)
if len(reponame) == 0 {
if len(reponame) == 0 {
data.ExitStatus = "err_repo_empty"
response.execute(data)
return
}
// init repo object (config vars)
// init repo object (config vars)
repo, err := s.gitea.Repo(usergitea, reponame)
if err != nil {
log.Printf("Web deploy module: Error loading config for gitea functionality: %v", err)
@ -72,7 +72,7 @@ func (s *server) giteaHandler(w http.ResponseWriter, r *http.Request) {
return
}
// GITEA TASKS (if create is selected)
if mode == "createrepo" {
if mode == "createrepo" {
// clean existing repo
if repo.Exists() {
@ -131,13 +131,13 @@ func (s *server) giteaHandler(w http.ResponseWriter, r *http.Request) {
data.ExitStatus = "repocreated-" + folder
} else if mode == "setfolder" {
if !repo.Exists() {
data.ExitStatus = "repo-nonexistent"
response.execute(data)
return
} else {
data.ExitStatus = folder
}
if !repo.Exists() {
data.ExitStatus = "repo-nonexistent"
response.execute(data)
return
} else {
data.ExitStatus = folder
}
}
log.Printf("Web deploy module: Set %s", folder)
@ -162,7 +162,7 @@ func (s *server) giteaHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Success: return reponame
// Success: return reponame
data.RepoName = reponame
// success

@ -0,0 +1,180 @@
package server
import (
"crypto/sha1"
"errors"
"fmt"
"log"
"net/http"
"strings"
"time"
"git.sindominio.net/sindominio/lowry/ldap"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"github.com/gorilla/mux"
"github.com/tv42/zbase32"
)
var (
ErrInvalidKey = errors.New("Key is not valid")
ErrNoKeyId = errors.New("The key doesn't contain a valid user id")
ErrExpiredKey = errors.New("The key is expired")
)
type openPGPdata struct {
Error string
Success bool
Key string
Fingerprint string
Expiry string
Collective string
}
func (s *Server) openPGPkeyHandler(w http.ResponseWriter, r *http.Request) {
response := s.newResponse("openpgp", w, r)
if response.User == "" {
http.Redirect(w, r, "/", http.StatusFound)
return
}
vars := mux.Vars(r)
collective := vars["name"]
var data openPGPdata
if r.Method == "POST" {
formKey := r.FormValue("key")
name := response.User
if collective != "" {
if isInCollective, _ := s.isUserinCollective(response.User, collective); !isInCollective {
log.Println("User", response.User, "doesn't have permissions to modify the openPGP key of", collective)
s.errorHandler(w, r)
return
}
name = collective
}
err := s.changeOpenPGPkey(name, formKey, collective != "")
if err != nil {
switch err {
case ErrInvalidKey:
data.Error = "invalid"
case ErrNoKeyId:
data.Error = "no-identity"
case ErrExpiredKey:
data.Error = "expired"
default:
s.errorHandler(w, r)
return
}
data.Key = formKey
} else {
data.Success = true
}
}
var key *ldap.OpenPGPkey
if collective != "" {
collectiveData, err := s.ldap.GetGroup(collective)
if err != nil {
log.Println("Error loading", collective, "profile:", err)
s.errorHandler(w, r)
return
}
key = collectiveData.OpenPGPkey
data.Collective = collective
} else {
userData, err := s.ldap.GetUser(response.User)
if err != nil {
log.Println("Error loading", response.User, "profile:", err)
s.errorHandler(w, r)
return
}
key = userData.OpenPGPkey
}
if key != nil {
if data.Key == "" {
var err error
data.Key, err = getArmoredKey(key.Key)
if err != nil {
log.Println("Error getting", response.User, "armored key:", err)
s.errorHandler(w, r)
return
}
}
data.Fingerprint = key.Fingerprint
if !key.Expiry.IsZero() {
data.Expiry = key.Expiry.Format("01/02/2006")
}
}
response.execute(data)
}
func (s *Server) changeOpenPGPkey(name, formKey string, collective bool) error {
key, err := crypto.NewKeyFromArmored(formKey)
if err != nil {
log.Println("Can't parse key for", name, ":", err)
return ErrInvalidKey
}
fingerprint := strings.ToUpper(key.GetFingerprint())
email := name + "@" + s.domain
found := false
for _, identity := range key.GetEntity().Identities {
if identity.UserId.Email == email {
found = true
break
}
}
if !found {
log.Println("User email", email, "not in key", fingerprint)
return ErrNoKeyId
}
pubKey, err := key.GetPublicKey()
if err != nil {
return err
}
expiry := getEntityExpiry(key)
if !expiry.IsZero() && expiry.Before(time.Now()) {
return ErrExpiredKey
}
wkdHash := fmt.Sprintf("%s@%s", calculateWKDhash(name), s.domain)
if collective {
err = s.ldap.ChangeGroupOpenPGPkey(name, fingerprint, expiry, pubKey, wkdHash)
} else {
err = s.ldap.ChangeUserOpenPGPkey(name, fingerprint, expiry, pubKey, wkdHash)
}
return err
}
func getArmoredKey(key []byte) (string, error) {