Merge branch '15881-ldap'
authorTom Clegg <tom@tomclegg.ca>
Thu, 14 May 2020 17:38:04 +0000 (13:38 -0400)
committerTom Clegg <tom@tomclegg.ca>
Thu, 14 May 2020 17:38:04 +0000 (13:38 -0400)
refs #15881

Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tom@tomclegg.ca>

1  2 
lib/config/config.default.yml
lib/config/deprecated.go
lib/config/deprecated_test.go
lib/config/generated_config.go
sdk/go/arvados/config.go
services/api/config/initializers/omniauth_init.rb

index 12f4bd9ded026471fe8fb8ed3d8641d1479d8b6e,a25b1f6109ce8d45bb5d1e59e563f3918970fd1f..0efe49c1cb9331621ebb6d141ef55697c52464fe
@@@ -524,54 -524,123 +524,123 @@@ Clusters
          MaxUUIDEntries:       1000
  
      Login:
-       # These settings are provided by your OAuth2 provider (eg
-       # Google) used to perform upstream authentication.
-       ProviderAppID: ""
-       ProviderAppSecret: ""
-       # (Experimental) Authenticate with Google, bypassing the
-       # SSO-provider gateway service. Use the Google Cloud console to
-       # enable the People API (APIs and Services > Enable APIs and
-       # services > Google People API > Enable), generate a Client ID
-       # and secret (APIs and Services > Credentials > Create
-       # credentials > OAuth client ID > Web application) and add your
-       # controller's /login URL (e.g.,
-       # "https://zzzzz.example.com/login") as an authorized redirect
-       # URL.
-       #
-       # Incompatible with ForceLegacyAPI14. ProviderAppID must be
-       # blank.
-       GoogleClientID: ""
-       GoogleClientSecret: ""
-       # Allow users to log in to existing accounts using any verified
-       # email address listed by their Google account. If true, the
-       # Google People API must be enabled in order for Google login to
-       # work. If false, only the primary email address will be used.
-       GoogleAlternateEmailAddresses: true
-       # (Experimental) Use PAM to authenticate logins, using the
-       # specified PAM service name.
-       #
-       # Cannot be used in combination with OAuth2 (ProviderAppID) or
-       # Google (GoogleClientID). Cannot be used on a cluster acting as
-       # a LoginCluster.
-       PAM: false
-       PAMService: arvados
-       # Domain name (e.g., "example.com") to use to construct the
-       # user's email address if PAM authentication returns a username
-       # with no "@". If empty, use the PAM username as the user's
-       # email address, whether or not it contains "@".
-       #
-       # Note that the email address is used as the primary key for
-       # user records when logging in. Therefore, if you change
-       # PAMDefaultEmailDomain after the initial installation, you
-       # should also update existing user records to reflect the new
-       # domain. Otherwise, next time those users log in, they will be
-       # given new accounts instead of accessing their existing
-       # accounts.
-       PAMDefaultEmailDomain: ""
+       # One of the following mechanisms (SSO, Google, PAM, LDAP, or
+       # LoginCluster) should be enabled; see
+       # https://doc.arvados.org/install/setup-login.html
+       Google:
+         # Authenticate with Google.
+         Enable: false
+         # Use the Google Cloud console to enable the People API (APIs
+         # and Services > Enable APIs and services > Google People API
+         # > Enable), generate a Client ID and secret (APIs and
+         # Services > Credentials > Create credentials > OAuth client
+         # ID > Web application) and add your controller's /login URL
+         # (e.g., "https://zzzzz.example.com/login") as an authorized
+         # redirect URL.
+         #
+         # Incompatible with ForceLegacyAPI14. ProviderAppID must be
+         # blank.
+         ClientID: ""
+         ClientSecret: ""
+         # Allow users to log in to existing accounts using any verified
+         # email address listed by their Google account. If true, the
+         # Google People API must be enabled in order for Google login to
+         # work. If false, only the primary email address will be used.
+         AlternateEmailAddresses: true
+       PAM:
+         # (Experimental) Use PAM to authenticate users.
+         Enable: false
+         # PAM service name. PAM will apply the policy in the
+         # corresponding config file (e.g., /etc/pam.d/arvados) or, if
+         # there is none, the default "other" config.
+         Service: arvados
+         # Domain name (e.g., "example.com") to use to construct the
+         # user's email address if PAM authentication returns a
+         # username with no "@". If empty, use the PAM username as the
+         # user's email address, whether or not it contains "@".
+         #
+         # Note that the email address is used as the primary key for
+         # user records when logging in. Therefore, if you change
+         # PAMDefaultEmailDomain after the initial installation, you
+         # should also update existing user records to reflect the new
+         # domain. Otherwise, next time those users log in, they will
+         # be given new accounts instead of accessing their existing
+         # accounts.
+         DefaultEmailDomain: ""
+       LDAP:
+         # Use an LDAP service to authenticate users.
+         Enable: false
+         # Server URL, like "ldap://ldapserver.example.com:389" or
+         # "ldaps://ldapserver.example.com:636".
+         URL: "ldap://ldap:389"
+         # Use StartTLS upon connecting to the server.
+         StartTLS: true
+         # Skip TLS certificate name verification.
+         InsecureTLS: false
+         # Strip the @domain part if a user supplies an email-style
+         # username with this domain. If "*", strip any user-provided
+         # domain. If "", never strip the domain part. Example:
+         # "example.com"
+         StripDomain: ""
+         # If, after applying StripDomain, the username contains no "@"
+         # character, append this domain to form an email-style
+         # username. Example: "example.com"
+         AppendDomain: ""
+         # The LDAP attribute to filter on when looking up a username
+         # (after applying StripDomain and AppendDomain).
+         SearchAttribute: uid
+         # Bind with this username (DN or UPN) and password when
+         # looking up the user record.
+         #
+         # Example user: "cn=admin,dc=example,dc=com"
+         SearchBindUser: ""
+         SearchBindPassword: ""
+         # Directory base for username lookup. Example:
+         # "ou=Users,dc=example,dc=com"
+         SearchBase: ""
+         # Additional filters for username lookup. Special characters
+         # in assertion values must be escaped (see RFC4515). Example:
+         # "(objectClass=person)"
+         SearchFilters: ""
+         # LDAP attribute to use as the user's email address.
+         #
+         # Important: This must not be an attribute whose value can be
+         # edited in the directory by the users themselves. Otherwise,
+         # users can take over other users' Arvados accounts trivially
+         # (email address is the primary key for Arvados accounts.)
+         EmailAttribute: mail
+         # LDAP attribute to use as the preferred Arvados username. If
+         # no value is found (or this config is empty) the username
+         # originally supplied by the user will be used.
+         UsernameAttribute: uid
+       SSO:
+         # Authenticate with a separate SSO server. (Deprecated)
+         Enable: false
+         # ProviderAppID and ProviderAppSecret are generated during SSO
+         # setup; see
+         # https://doc.arvados.org/v2.0/install/install-sso.html#update-config
+         ProviderAppID: ""
+         ProviderAppSecret: ""
  
        # The cluster ID to delegate the user database.  When set,
        # logins on this cluster will be redirected to the login cluster
        RunningJobLogRecordsToFetch: 2000
  
        # In systems with many shared projects, loading of dashboard and topnav
 -      # cab be slow due to collections indexing; use the following parameters
 +      # can be slow due to collections indexing; use the following parameters
        # to suppress these properties
        ShowRecentCollectionsOnDashboard: true
        ShowUserNotifications: true
diff --combined lib/config/deprecated.go
index bbbc9acf7534cd562c1e5dad0615fc688b07fd9b,3d62e7cc56e317f778674ef4558fa234aa6291a3..1be7208ee38facce00e71f2cfdf07885ccffde08
@@@ -23,6 -23,13 +23,13 @@@ type deprRequestLimits struct 
  type deprCluster struct {
        RequestLimits deprRequestLimits
        NodeProfiles  map[string]nodeProfile
+       Login         struct {
+               GoogleClientID                *string
+               GoogleClientSecret            *string
+               GoogleAlternateEmailAddresses *bool
+               ProviderAppID                 *string
+               ProviderAppSecret             *string
+       }
  }
  
  type deprecatedConfig struct {
@@@ -80,6 -87,34 +87,34 @@@ func (ldr *Loader) applyDeprecatedConfi
                if dst, n := &cluster.API.MaxRequestAmplification, dcluster.RequestLimits.MultiClusterRequestConcurrency; n != nil && *n != *dst {
                        *dst = *n
                }
+               // Google* moved to Google.*
+               if dst, n := &cluster.Login.Google.ClientID, dcluster.Login.GoogleClientID; n != nil && *n != *dst {
+                       *dst = *n
+                       if *n != "" {
+                               // In old config, non-empty ClientID meant enable
+                               cluster.Login.Google.Enable = true
+                       }
+               }
+               if dst, n := &cluster.Login.Google.ClientSecret, dcluster.Login.GoogleClientSecret; n != nil && *n != *dst {
+                       *dst = *n
+               }
+               if dst, n := &cluster.Login.Google.AlternateEmailAddresses, dcluster.Login.GoogleAlternateEmailAddresses; n != nil && *n != *dst {
+                       *dst = *n
+               }
+               // Provider* moved to SSO.Provider*
+               if dst, n := &cluster.Login.SSO.ProviderAppID, dcluster.Login.ProviderAppID; n != nil && *n != *dst {
+                       *dst = *n
+                       if *n != "" {
+                               // In old config, non-empty ID meant enable
+                               cluster.Login.SSO.Enable = true
+                       }
+               }
+               if dst, n := &cluster.Login.SSO.ProviderAppSecret, dcluster.Login.ProviderAppSecret; n != nil && *n != *dst {
+                       *dst = *n
+               }
                cfg.Clusters[id] = cluster
        }
        return nil
@@@ -100,7 -135,7 +135,7 @@@ func applyDeprecatedNodeProfile(hostnam
        if strings.HasPrefix(host, ":") {
                host = hostname + host
        }
 -      svc.InternalURLs[arvados.URL{Scheme: scheme, Host: host}] = arvados.ServiceInstance{}
 +      svc.InternalURLs[arvados.URL{Scheme: scheme, Host: host, Path: "/"}] = arvados.ServiceInstance{}
  }
  
  func (ldr *Loader) loadOldConfigHelper(component, path string, target interface{}) error {
@@@ -153,7 -188,6 +188,7 @@@ func loadOldClientConfig(cluster *arvad
        }
        if client.APIHost != "" {
                cluster.Services.Controller.ExternalURL.Host = client.APIHost
 +              cluster.Services.Controller.ExternalURL.Path = "/"
        }
        if client.Scheme != "" {
                cluster.Services.Controller.ExternalURL.Scheme = client.Scheme
@@@ -269,7 -303,7 +304,7 @@@ func (ldr *Loader) loadOldWebsocketConf
                cluster.PostgreSQL.ConnectionPool = *oc.PostgresPool
        }
        if oc.Listen != nil {
 -              cluster.Services.Websocket.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
 +              cluster.Services.Websocket.InternalURLs[arvados.URL{Host: *oc.Listen, Path: "/"}] = arvados.ServiceInstance{}
        }
        if oc.LogLevel != nil {
                cluster.SystemLogs.LogLevel = *oc.LogLevel
@@@ -328,7 -362,7 +363,7 @@@ func (ldr *Loader) loadOldKeepproxyConf
        loadOldClientConfig(cluster, oc.Client)
  
        if oc.Listen != nil {
 -              cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
 +              cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: *oc.Listen, Path: "/"}] = arvados.ServiceInstance{}
        }
        if oc.DefaultReplicas != nil {
                cluster.Collections.DefaultReplication = *oc.DefaultReplicas
@@@ -414,11 -448,11 +449,11 @@@ func (ldr *Loader) loadOldKeepWebConfig
        loadOldClientConfig(cluster, oc.Client)
  
        if oc.Listen != nil {
 -              cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
 -              cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
 +              cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: *oc.Listen, Path: "/"}] = arvados.ServiceInstance{}
 +              cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: *oc.Listen, Path: "/"}] = arvados.ServiceInstance{}
        }
        if oc.AttachmentOnlyHost != nil {
 -              cluster.Services.WebDAVDownload.ExternalURL = arvados.URL{Host: *oc.AttachmentOnlyHost}
 +              cluster.Services.WebDAVDownload.ExternalURL = arvados.URL{Host: *oc.AttachmentOnlyHost, Path: "/"}
        }
        if oc.ManagementToken != nil {
                cluster.ManagementToken = *oc.ManagementToken
index 8a49c2cf8d2c7b744712f809f525a2d82afa36b5,96eea4264cd1b288bee83c057ea9fa5e0f6f52b5..87e26fd09672805aa5bd840b757df71778477057
@@@ -89,6 -89,41 +89,41 @@@ Clusters
  `)
  }
  
+ func (s *LoadSuite) TestDeprecatedLoginBackend(c *check.C) {
+       checkEquivalent(c, `
+ Clusters:
+  z1111:
+   Login:
+    GoogleClientID: aaaa
+    GoogleClientSecret: bbbb
+    GoogleAlternateEmailAddresses: true
+ `, `
+ Clusters:
+  z1111:
+   Login:
+    Google:
+     Enable: true
+     ClientID: aaaa
+     ClientSecret: bbbb
+     AlternateEmailAddresses: true
+ `)
+       checkEquivalent(c, `
+ Clusters:
+  z1111:
+   Login:
+    ProviderAppID: aaaa
+    ProviderAppSecret: bbbb
+ `, `
+ Clusters:
+  z1111:
+   Login:
+    SSO:
+     Enable: true
+     ProviderAppID: aaaa
+     ProviderAppSecret: bbbb
+ `)
+ }
  func (s *LoadSuite) TestLegacyKeepWebConfig(c *check.C) {
        content := []byte(`
  {
        cluster, err := testLoadLegacyConfig(content, "-legacy-keepweb-config", c)
        c.Check(err, check.IsNil)
  
 -      c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
 +      c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com", Path: "/"})
        c.Check(cluster.SystemRootToken, check.Equals, "abcdefg")
  
        c.Check(cluster.Collections.WebDAVCache.TTL, check.Equals, arvados.Duration(60*time.Second))
        c.Check(cluster.Collections.WebDAVCache.MaxPermissionEntries, check.Equals, 100)
        c.Check(cluster.Collections.WebDAVCache.MaxUUIDEntries, check.Equals, 100)
  
 -      c.Check(cluster.Services.WebDAVDownload.ExternalURL, check.Equals, arvados.URL{Host: "download.example.com"})
 +      c.Check(cluster.Services.WebDAVDownload.ExternalURL, check.Equals, arvados.URL{Host: "download.example.com", Path: "/"})
        c.Check(cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: ":80"}], check.NotNil)
        c.Check(cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: ":80"}], check.NotNil)
  
@@@ -160,7 -195,7 +195,7 @@@ func (s *LoadSuite) TestLegacyKeepproxy
  
        c.Check(err, check.IsNil)
        c.Check(cluster, check.NotNil)
 -      c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
 +      c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com", Path: "/"})
        c.Check(cluster.SystemRootToken, check.Equals, "abcdefg")
        c.Check(cluster.ManagementToken, check.Equals, "xyzzy")
        c.Check(cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: ":80"}], check.Equals, arvados.ServiceInstance{})
@@@ -228,7 -263,7 +263,7 @@@ func (s *LoadSuite) TestLegacyArvGitHtt
  
        c.Check(err, check.IsNil)
        c.Check(cluster, check.NotNil)
 -      c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com"})
 +      c.Check(cluster.Services.Controller.ExternalURL, check.Equals, arvados.URL{Scheme: "https", Host: "example.com", Path: "/"})
        c.Check(cluster.SystemRootToken, check.Equals, "abcdefg")
        c.Check(cluster.ManagementToken, check.Equals, "xyzzy")
        c.Check(cluster.Git.GitCommand, check.Equals, "/test/git")
index 88cb9139a23e0361444969fd307bf34d399c05d5,6392472164872c13c1326ca01c4bf57bc34b603e..c9d29f814ddddc6d3d443006b823199d2f3e9e93
@@@ -530,54 -530,123 +530,123 @@@ Clusters
          MaxUUIDEntries:       1000
  
      Login:
-       # These settings are provided by your OAuth2 provider (eg
-       # Google) used to perform upstream authentication.
-       ProviderAppID: ""
-       ProviderAppSecret: ""
-       # (Experimental) Authenticate with Google, bypassing the
-       # SSO-provider gateway service. Use the Google Cloud console to
-       # enable the People API (APIs and Services > Enable APIs and
-       # services > Google People API > Enable), generate a Client ID
-       # and secret (APIs and Services > Credentials > Create
-       # credentials > OAuth client ID > Web application) and add your
-       # controller's /login URL (e.g.,
-       # "https://zzzzz.example.com/login") as an authorized redirect
-       # URL.
-       #
-       # Incompatible with ForceLegacyAPI14. ProviderAppID must be
-       # blank.
-       GoogleClientID: ""
-       GoogleClientSecret: ""
-       # Allow users to log in to existing accounts using any verified
-       # email address listed by their Google account. If true, the
-       # Google People API must be enabled in order for Google login to
-       # work. If false, only the primary email address will be used.
-       GoogleAlternateEmailAddresses: true
-       # (Experimental) Use PAM to authenticate logins, using the
-       # specified PAM service name.
-       #
-       # Cannot be used in combination with OAuth2 (ProviderAppID) or
-       # Google (GoogleClientID). Cannot be used on a cluster acting as
-       # a LoginCluster.
-       PAM: false
-       PAMService: arvados
-       # Domain name (e.g., "example.com") to use to construct the
-       # user's email address if PAM authentication returns a username
-       # with no "@". If empty, use the PAM username as the user's
-       # email address, whether or not it contains "@".
-       #
-       # Note that the email address is used as the primary key for
-       # user records when logging in. Therefore, if you change
-       # PAMDefaultEmailDomain after the initial installation, you
-       # should also update existing user records to reflect the new
-       # domain. Otherwise, next time those users log in, they will be
-       # given new accounts instead of accessing their existing
-       # accounts.
-       PAMDefaultEmailDomain: ""
+       # One of the following mechanisms (SSO, Google, PAM, LDAP, or
+       # LoginCluster) should be enabled; see
+       # https://doc.arvados.org/install/setup-login.html
+       Google:
+         # Authenticate with Google.
+         Enable: false
+         # Use the Google Cloud console to enable the People API (APIs
+         # and Services > Enable APIs and services > Google People API
+         # > Enable), generate a Client ID and secret (APIs and
+         # Services > Credentials > Create credentials > OAuth client
+         # ID > Web application) and add your controller's /login URL
+         # (e.g., "https://zzzzz.example.com/login") as an authorized
+         # redirect URL.
+         #
+         # Incompatible with ForceLegacyAPI14. ProviderAppID must be
+         # blank.
+         ClientID: ""
+         ClientSecret: ""
+         # Allow users to log in to existing accounts using any verified
+         # email address listed by their Google account. If true, the
+         # Google People API must be enabled in order for Google login to
+         # work. If false, only the primary email address will be used.
+         AlternateEmailAddresses: true
+       PAM:
+         # (Experimental) Use PAM to authenticate users.
+         Enable: false
+         # PAM service name. PAM will apply the policy in the
+         # corresponding config file (e.g., /etc/pam.d/arvados) or, if
+         # there is none, the default "other" config.
+         Service: arvados
+         # Domain name (e.g., "example.com") to use to construct the
+         # user's email address if PAM authentication returns a
+         # username with no "@". If empty, use the PAM username as the
+         # user's email address, whether or not it contains "@".
+         #
+         # Note that the email address is used as the primary key for
+         # user records when logging in. Therefore, if you change
+         # PAMDefaultEmailDomain after the initial installation, you
+         # should also update existing user records to reflect the new
+         # domain. Otherwise, next time those users log in, they will
+         # be given new accounts instead of accessing their existing
+         # accounts.
+         DefaultEmailDomain: ""
+       LDAP:
+         # Use an LDAP service to authenticate users.
+         Enable: false
+         # Server URL, like "ldap://ldapserver.example.com:389" or
+         # "ldaps://ldapserver.example.com:636".
+         URL: "ldap://ldap:389"
+         # Use StartTLS upon connecting to the server.
+         StartTLS: true
+         # Skip TLS certificate name verification.
+         InsecureTLS: false
+         # Strip the @domain part if a user supplies an email-style
+         # username with this domain. If "*", strip any user-provided
+         # domain. If "", never strip the domain part. Example:
+         # "example.com"
+         StripDomain: ""
+         # If, after applying StripDomain, the username contains no "@"
+         # character, append this domain to form an email-style
+         # username. Example: "example.com"
+         AppendDomain: ""
+         # The LDAP attribute to filter on when looking up a username
+         # (after applying StripDomain and AppendDomain).
+         SearchAttribute: uid
+         # Bind with this username (DN or UPN) and password when
+         # looking up the user record.
+         #
+         # Example user: "cn=admin,dc=example,dc=com"
+         SearchBindUser: ""
+         SearchBindPassword: ""
+         # Directory base for username lookup. Example:
+         # "ou=Users,dc=example,dc=com"
+         SearchBase: ""
+         # Additional filters for username lookup. Special characters
+         # in assertion values must be escaped (see RFC4515). Example:
+         # "(objectClass=person)"
+         SearchFilters: ""
+         # LDAP attribute to use as the user's email address.
+         #
+         # Important: This must not be an attribute whose value can be
+         # edited in the directory by the users themselves. Otherwise,
+         # users can take over other users' Arvados accounts trivially
+         # (email address is the primary key for Arvados accounts.)
+         EmailAttribute: mail
+         # LDAP attribute to use as the preferred Arvados username. If
+         # no value is found (or this config is empty) the username
+         # originally supplied by the user will be used.
+         UsernameAttribute: uid
+       SSO:
+         # Authenticate with a separate SSO server. (Deprecated)
+         Enable: false
+         # ProviderAppID and ProviderAppSecret are generated during SSO
+         # setup; see
+         # https://doc.arvados.org/v2.0/install/install-sso.html#update-config
+         ProviderAppID: ""
+         ProviderAppSecret: ""
  
        # The cluster ID to delegate the user database.  When set,
        # logins on this cluster will be redirected to the login cluster
        RunningJobLogRecordsToFetch: 2000
  
        # In systems with many shared projects, loading of dashboard and topnav
 -      # cab be slow due to collections indexing; use the following parameters
 +      # can be slow due to collections indexing; use the following parameters
        # to suppress these properties
        ShowRecentCollectionsOnDashboard: true
        ShowUserNotifications: true
diff --combined sdk/go/arvados/config.go
index 69de3f05e231d29321ddbaa0b0f5f6dc1d5659e0,7ab8d9e1f64e0260125a010be633062cb4305fa9..9f9f00e6445ec676b7ca19877cef1e7b304912e2
@@@ -135,16 -135,39 +135,39 @@@ type Cluster struct 
                Repositories string
        }
        Login struct {
-               GoogleClientID                string
-               GoogleClientSecret            string
-               GoogleAlternateEmailAddresses bool
-               PAM                           bool
-               PAMService                    string
-               PAMDefaultEmailDomain         string
-               ProviderAppID                 string
-               ProviderAppSecret             string
-               LoginCluster                  string
-               RemoteTokenRefresh            Duration
+               LDAP struct {
+                       Enable             bool
+                       URL                URL
+                       StartTLS           bool
+                       InsecureTLS        bool
+                       StripDomain        string
+                       AppendDomain       string
+                       SearchAttribute    string
+                       SearchBindUser     string
+                       SearchBindPassword string
+                       SearchBase         string
+                       SearchFilters      string
+                       EmailAttribute     string
+                       UsernameAttribute  string
+               }
+               Google struct {
+                       Enable                  bool
+                       ClientID                string
+                       ClientSecret            string
+                       AlternateEmailAddresses bool
+               }
+               PAM struct {
+                       Enable             bool
+                       Service            string
+                       DefaultEmailDomain string
+               }
+               SSO struct {
+                       Enable            bool
+                       ProviderAppID     string
+                       ProviderAppSecret string
+               }
+               LoginCluster       string
+               RemoteTokenRefresh Duration
        }
        Mail struct {
                MailchimpAPIKey                string
@@@ -303,10 -326,6 +326,10 @@@ func (su *URL) UnmarshalText(text []byt
        u, err := url.Parse(string(text))
        if err == nil {
                *su = URL(*u)
 +              if su.Path == "" && su.Host != "" {
 +                      // http://example really means http://example/
 +                      su.Path = "/"
 +              }
        }
        return err
  }
index 35a318b94fe1f5bf4bc822f06608c8b031854a1e,5557be1dcdb1502ed51df7b5b0f9de7854a9a1f3..a1b2356bd56242389cff2dac821ad7d68f103177
@@@ -9,14 -9,14 +9,14 @@@
  
  if defined? CUSTOM_PROVIDER_URL
    Rails.logger.warn "Copying omniauth from globals in legacy config file."
-   Rails.configuration.Login["ProviderAppID"] = APP_ID
-   Rails.configuration.Login["ProviderAppSecret"] = APP_SECRET
+   Rails.configuration.Login["SSO"]["ProviderAppID"] = APP_ID
+   Rails.configuration.Login["SSO"]["ProviderAppSecret"] = APP_SECRET
 -  Rails.configuration.Services["SSO"]["ExternalURL"] = CUSTOM_PROVIDER_URL
 +  Rails.configuration.Services["SSO"]["ExternalURL"] = CUSTOM_PROVIDER_URL.sub(/\/$/, "") + "/"
  else
    Rails.application.config.middleware.use OmniAuth::Builder do
      provider(:josh_id,
-              Rails.configuration.Login["ProviderAppID"],
-              Rails.configuration.Login["ProviderAppSecret"],
+              Rails.configuration.Login["SSO"]["ProviderAppID"],
+              Rails.configuration.Login["SSO"]["ProviderAppSecret"],
               Rails.configuration.Services["SSO"]["ExternalURL"])
    end
    OmniAuth.config.on_failure = StaticController.action(:login_failure)