Commit f20eaef0 authored by ale's avatar ale

Add SSO group ACLs to the SAML-bridge provider configuration

One can now restrict providers to specific SSO groups.
parent 3ec9dca3
Pipeline #3940 passed with stages
in 4 minutes and 28 seconds
......@@ -10,6 +10,7 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
......@@ -24,6 +25,13 @@ import (
"git.autistici.org/id/go-sso/httpsso"
)
type serviceProvider struct {
Descriptor string `yaml:"descriptor"`
SSOGroups []string `yaml:"sso_groups"`
parsed *saml.EntityDescriptor
}
type Config struct {
BaseURL string `yaml:"base_url"`
......@@ -41,8 +49,8 @@ type Config struct {
SSODomain string `yaml:"sso_domain"`
// Service provider config.
ServiceProviders []string `yaml:"service_providers"`
parsedServiceProviders map[string]*saml.EntityDescriptor
ServiceProviders []*serviceProvider `yaml:"service_providers"`
serviceProviderMap map[string]*serviceProvider
}
// Sanity checks for the configuration.
......@@ -71,9 +79,9 @@ func (c *Config) check() error {
}
func (c *Config) loadServiceProviders() error {
c.parsedServiceProviders = make(map[string]*saml.EntityDescriptor)
for _, path := range c.ServiceProviders {
data, err := ioutil.ReadFile(path)
c.serviceProviderMap = make(map[string]*serviceProvider)
for _, sp := range c.ServiceProviders {
data, err := ioutil.ReadFile(sp.Descriptor)
if err != nil {
return err
}
......@@ -81,17 +89,26 @@ func (c *Config) loadServiceProviders() error {
if err := xml.Unmarshal(data, &ent); err != nil {
return err
}
c.parsedServiceProviders[ent.EntityID] = &ent
sp.parsed = &ent
c.serviceProviderMap[ent.EntityID] = sp
}
return nil
}
func (c *Config) GetServiceProvider(r *http.Request, serviceProviderID string) (*saml.EntityDescriptor, error) {
srv, ok := c.parsedServiceProviders[serviceProviderID]
sp, ok := c.serviceProviderMap[serviceProviderID]
if !ok {
return nil, os.ErrNotExist
}
return srv, nil
return sp.parsed, nil
}
func (c *Config) GetSSOGroups(serviceProviderID string) []string {
sp, ok := c.serviceProviderMap[serviceProviderID]
if !ok {
return nil
}
return sp.SSOGroups
}
// Read users from a YAML-encoded file, in a format surprisingly
......@@ -106,11 +123,12 @@ type userInfo struct {
}
type userFileBackend struct {
users map[string]userInfo
config *Config
users map[string]userInfo
}
func newUserFileBackend(path string) (*userFileBackend, error) {
data, err := ioutil.ReadFile(path)
func newUserFileBackend(config *Config) (*userFileBackend, error) {
data, err := ioutil.ReadFile(config.UsersFile)
if err != nil {
return nil, err
}
......@@ -122,22 +140,51 @@ func newUserFileBackend(path string) (*userFileBackend, error) {
for _, u := range userList {
users[u.Name] = u
}
return &userFileBackend{users}, nil
return &userFileBackend{
config: config,
users: users,
}, nil
}
// Nice little O(N^2) algorithm right there...
func matchGroups(user, exp []string) bool {
if exp == nil {
return true
}
for _, ug := range user {
for _, eg := range exp {
if ug == eg {
return true
}
}
}
return false
}
func (b *userFileBackend) GetSession(w http.ResponseWriter, r *http.Request, req *saml.IdpAuthnRequest) *saml.Session {
// The request should have the X-Authenticated-User header.
username := r.Header.Get("X-Authenticated-User")
// 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
// configuration, is also verified at this stage.
username := httpsso.Username(r)
if username == "" {
http.Error(w, "No user found", http.StatusInternalServerError)
return nil
}
if !matchGroups(httpsso.Groups(r), b.config.GetSSOGroups(req.ServiceProviderMetadata.ID)) {
http.Error(w, "Forbidden (bad group)", http.StatusForbidden)
return nil
}
user, ok := b.users[username]
if !ok {
http.Error(w, "User not found", http.StatusInternalServerError)
return nil
}
log.Printf("successfully authenticated session for username=%s, provider=%s", username, req.ServiceProviderMetadata.ID)
return &saml.Session{
ID: base64.StdEncoding.EncodeToString(randomBytes(32)),
CreateTime: saml.TimeNow(),
......@@ -186,7 +233,7 @@ func NewSAMLIDP(config *Config) (http.Handler, error) {
svc += "/"
}
users, err := newUserFileBackend(config.UsersFile)
users, err := newUserFileBackend(config)
if err != nil {
return nil, err
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment