Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

LDAP Authentication #346

Open
wants to merge 79 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
cf3f666
Added LDAP link
Aug 5, 2023
c5b35a6
Added LDAP config template
Aug 5, 2023
ae9f95a
Added LDAP route
Aug 5, 2023
13bb999
Made TLS checkbox work.
Aug 6, 2023
830ab09
Fixed typo
Aug 6, 2023
98be763
Added saving of LDAP settings
Aug 6, 2023
1fab192
Added TODO
Aug 6, 2023
23e797e
Addd default value for LDAP config page
Aug 6, 2023
6b2f853
Removed eURL file
Aug 6, 2023
6602495
Added a way of disabling LDAP auth
Aug 6, 2023
6c8f5fa
Added go-ldap dep
Aug 6, 2023
fdb2ec6
Added LDAP auth if there is a user
Aug 6, 2023
d833859
Added LDAP user creation
Aug 6, 2023
79b28ef
Fixed ineffassign to
Aug 6, 2023
9c56bf6
Fixed for LDAP connection
Aug 6, 2023
1fd8c9b
Fix else if (`gocritic`)
Aug 6, 2023
23d23c7
Fixed r param `revive`
Aug 6, 2023
80f5fac
Added `c.DB.SetSetting` (`errcheck`)
Aug 6, 2023
e5f815c
Fixed filter labels
Aug 6, 2023
a558947
Fixed `SetSetting` err checking
Aug 6, 2023
c317046
Merge branch 'sentriz:master' into ldap_auth
betapictoris Aug 15, 2023
fc08e0a
Merge branch 'sentriz:master' into ldap_auth
betapictoris Sep 5, 2023
46dac6d
Check errors in ldap do handler
betapictoris Sep 9, 2023
94027dc
Fixed variable defs
betapictoris Sep 9, 2023
e7d6fda
Fixed bug where two requests got merged
betapictoris Sep 9, 2023
7de6cf1
Fixed use of `newLDAPUser`
betapictoris Sep 9, 2023
238856a
Fixed `newLDAPUser` def
betapictoris Sep 9, 2023
9e5b8d4
Fixed `newLDAPUser` being reset
betapictoris Sep 9, 2023
d6d51ed
Update username error message to match Subsonic API
betapictoris Oct 27, 2023
38ea72b
Only try to login if there is exactly 1 user, otherwise return an error.
betapictoris Oct 27, 2023
667cf73
Extract out LDAP connection creation
betapictoris Oct 27, 2023
a99d63a
Fixed nil password causing invalid dereferences
betapictoris Oct 27, 2023
c03e104
Fixed merge conflicts
betapictoris Oct 29, 2023
a24b7c0
Merge branch `master` into `ldap_auth`
betapictoris Nov 10, 2023
e770eaa
Fixed old references to use the new style
betapictoris Nov 10, 2023
7558bb3
Merge branch 'master' into ldap_auth
betapictoris Nov 12, 2023
322aa07
Made `WithUser` middleware private
betapictoris Nov 30, 2023
4799fb2
Update `go-ldap/ldap` to v3
betapictoris Dec 1, 2023
df42923
Made LDAP query log message match others
betapictoris Dec 9, 2023
7c6e195
Simplified code using gofmt
betapictoris Dec 9, 2023
84e29da
Merge branch 'master' into ldap_auth
betapictoris Dec 9, 2023
0514996
Used `log` to log instead of `fmt`
betapictoris Dec 9, 2023
90b5324
`gofmt` handlers
betapictoris Dec 9, 2023
f8b0fbb
Merge branch 'ldap_auth' into master
betapictoris Feb 15, 2024
e944817
Merge with upstream
betapictoris Feb 15, 2024
408d325
Move LDAP into a seperate file
betapictoris Feb 18, 2024
6301cb2
Fix `User` struct from having `not null` passwords
betapictoris Feb 18, 2024
fdceb55
Removed unused logs
betapictoris Feb 18, 2024
f6ecd6d
Added dashboard login for LDAP users
betapictoris Feb 18, 2024
bc62b21
Lower-cased and removed punctuation for errors
betapictoris Feb 18, 2024
683525a
Removed unused password parameter from `createUserFromLDAP`
betapictoris Feb 18, 2024
ac31258
Removed unnecessary trailing newline
betapictoris Feb 18, 2024
8793a36
Rewrote if-else to switch for LDAP results
betapictoris Feb 18, 2024
0d85e0c
Stopped opening up a second connection when creating a user from LDAP
betapictoris Feb 18, 2024
8d4c22a
Merge branch 'master' into ldap_auth
betapictoris Feb 22, 2024
9a97fa4
Merge branch 'master' into ldap_auth
betapictoris Feb 23, 2024
02fd314
Merge branch 'sentriz:master' into ldap_auth
betapictoris Feb 28, 2024
c531666
Bump LDAP deps
betapictoris Apr 1, 2024
3cf6d0a
Added LDAP config
betapictoris Apr 1, 2024
dc3682d
Removed LDAP web UI
betapictoris Apr 1, 2024
8ba05ef
Reverted home template
betapictoris Apr 1, 2024
6bf39cc
Removed LDAP config from admin templates
betapictoris Apr 1, 2024
6d7c605
Merge branch 'master' into ldap_auth
betapictoris Apr 1, 2024
6eddc2a
Made withUser return a middleware and take dbc
betapictoris Apr 7, 2024
995c566
Readded gofeed dep
betapictoris Apr 7, 2024
b36f26e
Have LDAP read from config
betapictoris Apr 7, 2024
6ca68b2
Only allow use of subsonic API with one auth method
betapictoris Apr 7, 2024
5638ef5
Added LDAP admin filter
betapictoris Apr 7, 2024
fccf016
Added LDAP password cache
betapictoris Apr 7, 2024
f5195f1
Fix password check for local users
betapictoris Apr 7, 2024
0f7e24b
Fixed filter params
betapictoris Apr 8, 2024
e2415e9
Do session change on failed LDAP check
betapictoris Apr 9, 2024
dc98b6a
Merge branch 'sentriz:master' into ldap_auth
betapictoris Apr 9, 2024
f812c15
Merge branch 'master' into ldap_auth
betapictoris Jun 11, 2024
7159e1c
fix: Fixed conf music path check
betapictoris Jun 26, 2024
4c5c015
go mod tidy
betapictoris Jun 26, 2024
758c4c7
lint: Fixed ineffassign on Add
betapictoris Jun 26, 2024
21b562b
style: Made invalid auth chain less chain-y
betapictoris Jun 26, 2024
d9facdc
Merge branch 'master' into ldap_auth
betapictoris Sep 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.db
*.db.*.bak
*db-wal
*db-shm
*.sql
Expand Down
23 changes: 21 additions & 2 deletions cmd/gonic/gonic.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"go.senan.xyz/gonic/infocache/artistinfocache"
"go.senan.xyz/gonic/jukebox"
"go.senan.xyz/gonic/lastfm"
"go.senan.xyz/gonic/ldap"
"go.senan.xyz/gonic/listenbrainz"
"go.senan.xyz/gonic/playlist"
"go.senan.xyz/gonic/podcast"
Expand Down Expand Up @@ -83,6 +84,24 @@ func main() {

confExcludePattern := flag.String("exclude-pattern", "", "regex pattern to exclude files from scan (optional)")

var ldapConfig ldap.Config
flag.StringVar(&ldapConfig.BindUser, "ldap-bind-user", "", "the bind user to bind to LDAP with (required for LDAP)")
flag.StringVar(&ldapConfig.BindPass, "ldap-bind-pass", "", "the password of the LDAP bind user (required for LDAP)")
flag.StringVar(&ldapConfig.BaseDN, "ldap-base-dn", "", "the base DN for LDAP objects (required for LDAP)")

flag.StringVar(&ldapConfig.Filter, "ldap-filter", "", "the filter to select LDAP objects with (optional)")
flag.StringVar(&ldapConfig.AdminFilter, "ldap-admin-filter", "(memberof=cn=admin)", "the filter to select LDAP objects with (optional)")

flag.StringVar(&ldapConfig.FQDN, "ldap-fqdn", "", "the name of the server to connect to (required for LDAP)")
flag.UintVar(&ldapConfig.Port, "ldap-port", 389, "what port the LDAP server is hosted on (optional)")
flag.BoolVar(&ldapConfig.TLS, "ldap-tls", false, "whether gonic will connect to the LDAP server using TLS (optional)")

if ldapConfig.FQDN != "" {
if ldapConfig.BindUser == "" || ldapConfig.BindPass == "" || ldapConfig.BaseDN == "" {
log.Fatal("a server was provided for an LDAP connection, but configuration is incomplete")
}
}

var confMultiValueGenre, confMultiValueArtist, confMultiValueAlbumArtist multiValueSetting
flag.Var(&confMultiValueGenre, "multi-value-genre", "setting for mutli-valued genre scanning (optional)")
flag.Var(&confMultiValueArtist, "multi-value-artist", "setting for mutli-valued track artist scanning (optional)")
Expand Down Expand Up @@ -250,11 +269,11 @@ func main() {
return url.String()
}

ctrlAdmin, err := ctrladmin.New(dbc, sessDB, scannr, podcast, lastfmClient, resolveProxyPath)
ctrlAdmin, err := ctrladmin.New(dbc, sessDB, scannr, podcast, lastfmClient, resolveProxyPath, ldapConfig)
if err != nil {
log.Panicf("error creating admin controller: %v\n", err)
}
ctrlSubsonic, err := ctrlsubsonic.New(dbc, scannr, musicPaths, *confPodcastPath, cacheDirAudio, cacheDirCovers, jukebx, playlistStore, scrobblers, podcast, transcoder, lastfmClient, artistInfoCache, albumInfoCache, resolveProxyPath)
ctrlSubsonic, err := ctrlsubsonic.New(dbc, scannr, musicPaths, *confPodcastPath, cacheDirAudio, cacheDirCovers, jukebx, playlistStore, scrobblers, podcast, transcoder, lastfmClient, artistInfoCache, albumInfoCache, resolveProxyPath, ldapConfig)
if err != nil {
log.Panicf("error creating subsonic controller: %v\n", err)
}
Expand Down
2 changes: 1 addition & 1 deletion db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ type User struct {
ID int `gorm:"primary_key"`
CreatedAt time.Time
Name string `gorm:"not null; unique_index" sql:"default: null"`
Password string `gorm:"not null" sql:"default: null"`
Password string `sql:"default: null"`
LastFMSession string `sql:"default: null"`
ListenBrainzURL string `sql:"default: null"`
ListenBrainzToken string `sql:"default: null"`
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/dustin/go-humanize v1.0.1
github.com/fatih/structs v1.1.0
github.com/fsnotify/fsnotify v1.7.0
github.com/go-ldap/ldap/v3 v3.4.6
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/google/uuid v1.6.0
github.com/gorilla/securecookie v1.1.2
Expand All @@ -34,10 +35,12 @@ require (
)

require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/PuerkitoBio/goquery v1.9.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/gorilla/context v1.1.2 // indirect
Expand Down
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
Expand Down Expand Up @@ -31,6 +33,10 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-asn1-ber/asn1-ber v1.5.5 h1:MNHlNMBDgEKD4TcKr36vQN68BA00aDfjIt3/bD50WnA=
github.com/go-asn1-ber/asn1-ber v1.5.5/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.6 h1:ert95MdbiG7aWo/oPYp9btL3KJlMPKnP58r09rI8T+A=
github.com/go-ldap/ldap/v3 v3.4.6/go.mod h1:IGMQANNtxpsOzj7uUAMjpGBaOVTC4DYyIy8VsTdxmtc=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
Expand All @@ -48,6 +54,7 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
Expand Down Expand Up @@ -135,7 +142,10 @@ github.com/sentriz/gormstore v0.0.0-20220105134332-64e31f7f6981/go.mod h1:Rx8XB1
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand Down Expand Up @@ -186,6 +196,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
Expand All @@ -210,6 +222,7 @@ gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
jaytaylor.com/html2text v0.0.0-20230321000545-74c2419ad056 h1:6YFJoB+0fUH6X3xU/G2tQqCYg+PkGtnZ5nMR5rpw72g=
Expand Down
226 changes: 226 additions & 0 deletions ldap/ldap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
package ldap

import (
"errors"
"fmt"
"log"
"time"

"go.senan.xyz/gonic/db"

"github.com/go-ldap/ldap/v3"
)

var ldapStore = make(LDAPStore)

Check failure on line 14 in ldap/ldap.go

View workflow job for this annotation

GitHub Actions / Lint and test

ldapStore is a global variable (gochecknoglobals)

// LDAPStore maps users to a cached password
type LDAPStore map[string]CachedLDAPpassword

// Add caches a password username set.
func (store LDAPStore) Add(username, password string) {
store[username] = CachedLDAPpassword{
Password: password,
ExpiresAt: time.Now().Add(time.Hour * 8), // Keep the password valid for 8 hours.
}
}

// IsValid checks if a user's password is stored in the cache and checks if a
// given password is valid.
func (store LDAPStore) IsValid(username, password string) bool {
cached, ok := store[username]
if !ok {
return false
}

if cached.Password != password {
return false
}

return cached.IsValid()
}

// CachedLDAPpassword stores an LDAP user's password and a time at which the
// server should no longer accept it.
type CachedLDAPpassword struct {
Password string
ExpiresAt time.Time
}

func (password CachedLDAPpassword) IsValid() bool {
return password.ExpiresAt.After(time.Now())
}

// Cofig stores the user's LDAP server options.
type Config struct {
BindUser string
BindPass string
BaseDN string

Filter string
AdminFilter string

FQDN string
Port uint
TLS bool
}

func (c Config) IsSetup() bool {
// This is basically checking if LDAP is setup, if ldapFQDN isn't set we can
// assume that the user hasn't configured LDAP.
return c.FQDN != ""
}

func CheckLDAPcreds(username string, password string, dbc *db.DB, config Config) (bool, error) {
if !config.IsSetup() {
return false, nil
}

if ldapStore.IsValid(username, password) {
log.Println("Password authenticated via cache!")
return true, nil
}

log.Println("Checking password against LDAP server ...")

// Now, we can try to connect to the LDAP server.
l, err := createLDAPconnection(config)
if err != nil {
// Return a generic error.
log.Println("Failed to connect to LDAP server:", err)
return false, errors.New("failed to connect to LDAP server")
}
defer l.Close()

// Create the user if it doesn't exist on the database already.
err = createUserFromLDAP(username, dbc, config, l)
if err != nil {
log.Println("Failed to create user from LDAP:", err)
return false, err
}

// After we have a connection, let's try binding
_, err = l.SimpleBind(&ldap.SimpleBindRequest{
Username: fmt.Sprintf("uid=%s,%s", username, config.BaseDN),
Password: password,
})

if err == nil {
// Authentication was OK
ldapStore.Add(username, password)
return true, nil
}

log.Println("Failed to bind to LDAP server:", err)
return false, nil
}

// Creates a user from creds
func createUserFromLDAP(username string, dbc *db.DB, config Config, l *ldap.Conn) error {
user := dbc.GetUserByName(username)
if user != nil {
return nil
}

if !config.IsSetup() {
return nil
}

isAdmin := doesLDAPAdminExist(username, config, l)
log.Println(username, isAdmin)

if doesLDAPUserExist(username, config, l) && !isAdmin {
return errors.New("no such user")
}

newUser := db.User{
Name: username,
Password: "", // no password because we want auth to fail.
IsAdmin: isAdmin,
}

err := dbc.Create(&newUser).Error
if err != nil {
return err
}

log.Println("User created via LDAP:", username)
return nil
}

// doesLDAPAdminExist checks if an admin exists on the server.
func doesLDAPAdminExist(username string, config Config, l *ldap.Conn) bool {
filter := fmt.Sprintf("(&(uid=%s)%s)", ldap.EscapeFilter(username), config.AdminFilter)

searchReq := ldap.NewSearchRequest(
config.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
[]string{"dn"},
nil,
)

result, err := l.Search(searchReq)
if err != nil {
log.Println("failed to query LDAP server:", err)
return false
}

if len(result.Entries) == 1 {
return true
}

return false
}

// doesLDAPUserExist checks if a user exists on the server.
func doesLDAPUserExist(username string, config Config, l *ldap.Conn) bool {
filter := fmt.Sprintf("(&(uid=%s)%s)", ldap.EscapeFilter(username), config.Filter)

searchReq := ldap.NewSearchRequest(
config.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
filter,
[]string{"dn"},
nil,
)

result, err := l.Search(searchReq)
if err != nil {
log.Println("failed to query LDAP server:", err)
return false
}

if len(result.Entries) == 1 {
return true
}

return false
}

// Creates a connection to an LDAP server.
func createLDAPconnection(config Config) (*ldap.Conn, error) {
protocol := "ldap"
if config.TLS {
protocol = "ldaps"
}

// Now, we can try to connect to the LDAP server.
l, err := ldap.DialURL(fmt.Sprintf("%s://%s:%d", protocol, config.FQDN, config.Port))
if err != nil {
// Warn the server and return the error.
log.Println("Failed to connect to LDAP server", err)
return nil, err
}

// After we have a connection, let's try binding
_, err = l.SimpleBind(&ldap.SimpleBindRequest{
Username: fmt.Sprintf("uid=%s,%s", config.BindUser, config.BaseDN),
Password: config.BindPass,
})
if err != nil {
log.Println("Failed to bind to LDAP:", err)
return nil, errors.New("wrong username or password")
}

return l, nil
}
5 changes: 4 additions & 1 deletion server/ctrladmin/ctrl.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/handlerutil"
"go.senan.xyz/gonic/lastfm"
"go.senan.xyz/gonic/ldap"
"go.senan.xyz/gonic/podcast"
"go.senan.xyz/gonic/scanner"
"go.senan.xyz/gonic/server/ctrladmin/adminui"
Expand All @@ -48,11 +49,12 @@ type Controller struct {
podcasts *podcast.Podcasts
lastfmClient *lastfm.Client
resolveProxyPath ProxyPathResolver
ldapConfig ldap.Config
}

type ProxyPathResolver func(in string) string

func New(dbc *db.DB, sessDB *gormstore.Store, scanner *scanner.Scanner, podcasts *podcast.Podcasts, lastfmClient *lastfm.Client, resolveProxyPath ProxyPathResolver) (*Controller, error) {
func New(dbc *db.DB, sessDB *gormstore.Store, scanner *scanner.Scanner, podcasts *podcast.Podcasts, lastfmClient *lastfm.Client, resolveProxyPath ProxyPathResolver, ldapConfig ldap.Config) (*Controller, error) {
c := Controller{
ServeMux: http.NewServeMux(),

Expand All @@ -62,6 +64,7 @@ func New(dbc *db.DB, sessDB *gormstore.Store, scanner *scanner.Scanner, podcasts
podcasts: podcasts,
lastfmClient: lastfmClient,
resolveProxyPath: resolveProxyPath,
ldapConfig: ldapConfig,
}

resp := respHandler(adminui.TemplatesFS, resolveProxyPath)
Expand Down
Loading
Loading