diff --git a/saml/saml.go b/saml/saml.go index c31e52468248bd2d867620251decb4be50cb8769..64a39e9bcbb5c518185c594f965551328ff3eec8 100644 --- a/saml/saml.go +++ b/saml/saml.go @@ -41,7 +41,8 @@ type serviceProvider struct { type Config struct { BaseURL string `yaml:"base_url"` - UsersFile string `yaml:"users_file"` + UsersFile string `yaml:"users_file"` + EmailUsernames bool `yaml:"email_usernames"` // SAML X509 credentials. CertificateFile string `yaml:"certificate_file"` @@ -134,25 +135,63 @@ type userInfo struct { Email string `yaml:"email"` } +type userBackend interface { + GetUser(string) (*userInfo, bool) +} + type userFileBackend struct { - config *Config - users map[string]userInfo + users map[string]*userInfo +} + +func (b *userFileBackend) GetUser(username string) (*userInfo, bool) { + info, ok := b.users[username] + return info, ok } -func newUserFileBackend(config *Config) (*userFileBackend, error) { - data, err := ioutil.ReadFile(config.UsersFile) +func newUserFileBackend(path string) (*userFileBackend, error) { + data, err := ioutil.ReadFile(path) if err != nil { return nil, err } - var userList []userInfo + var userList []*userInfo if err := yaml.Unmarshal(data, &userList); err != nil { return nil, err } - users := make(map[string]userInfo) + users := make(map[string]*userInfo) for _, u := range userList { users[u.Name] = u } return &userFileBackend{ + users: users, + }, nil +} + +type identityUserBackend struct{} + +func (b *identityUserBackend) GetUser(username string) (*userInfo, bool) { + return &userInfo{Name: username, Email: username}, true +} + +type sessionProvider struct { + config *Config + users userBackend +} + +func newSessionProvider(config *Config) (*sessionProvider, error) { + var users userBackend + var err error + switch { + case config.UsersFile != "": + users, err = newUserFileBackend(config.UsersFile) + if err != nil { + return nil, err + } + case config.EmailUsernames: + users = new(identityUserBackend) + default: + return nil, errors.New("neither users_file or email_usernames are specified") + } + return &sessionProvider{ config: config, users: users, }, nil @@ -173,7 +212,7 @@ func matchGroups(user, exp []string) bool { return false } -func (b *userFileBackend) GetSession(w http.ResponseWriter, r *http.Request, req *saml.IdpAuthnRequest) *saml.Session { +func (sp *sessionProvider) GetSession(w http.ResponseWriter, r *http.Request, req *saml.IdpAuthnRequest) *saml.Session { // Check for authentication by verifying the SSO username. We // also need to be able to retrieve user information from the // backend, to match SSO. Group membership, if enabled in our @@ -185,12 +224,12 @@ func (b *userFileBackend) GetSession(w http.ResponseWriter, r *http.Request, req return nil } - if !matchGroups(httpsso.Groups(r), b.config.GetSSOGroups(req.ServiceProviderMetadata.ID)) { + if !matchGroups(httpsso.Groups(r), sp.config.GetSSOGroups(req.ServiceProviderMetadata.ID)) { http.Error(w, "Forbidden (bad group)", http.StatusForbidden) return nil } - user, ok := b.users[username] + user, ok := sp.users.GetUser(username) if !ok { log.Printf("error: user %s is authenticated but unknown", username) http.Error(w, "User not found", http.StatusInternalServerError) @@ -252,7 +291,7 @@ func NewSAMLIDP(config *Config) (http.Handler, error) { svc += "/" } - users, err := newUserFileBackend(config) + sp, err := newSessionProvider(config) if err != nil { return nil, err } @@ -267,7 +306,7 @@ func NewSAMLIDP(config *Config) (http.Handler, error) { MetadataURL: metadataURL, SSOURL: ssoURL, ServiceProviderProvider: config, - SessionProvider: users, + SessionProvider: sp, } h := idp.Handler()