mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 23:20:24 +00:00
oidc: allow to configure oauth2 scopes
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
371012a46e
commit
6b995db864
9 changed files with 49 additions and 10 deletions
|
@ -105,6 +105,7 @@ var (
|
|||
UsernameField: "",
|
||||
RoleField: "",
|
||||
ImplicitRoles: false,
|
||||
Scopes: []string{"openid", "profile", "email"},
|
||||
CustomFields: []string{},
|
||||
},
|
||||
Security: httpd.SecurityConf{
|
||||
|
@ -1408,6 +1409,12 @@ func getHTTPDOIDCFromEnv(idx int) (httpd.OIDC, bool) {
|
|||
isSet = true
|
||||
}
|
||||
|
||||
scopes, ok := lookupStringListFromEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__OIDC__SCOPES", idx))
|
||||
if ok {
|
||||
result.Scopes = scopes
|
||||
isSet = true
|
||||
}
|
||||
|
||||
roleField, ok := os.LookupEnv(fmt.Sprintf("SFTPGO_HTTPD__BINDINGS__%v__OIDC__ROLE_FIELD", idx))
|
||||
if ok {
|
||||
result.RoleField = roleField
|
||||
|
|
|
@ -1060,6 +1060,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__REDIRECT_BASE_URL", "redirect base url")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__USERNAME_FIELD", "preferred_username")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__ROLE_FIELD", "sftpgo_role")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__SCOPES", "openid")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__IMPLICIT_ROLES", "1")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__CUSTOM_FIELDS", "field1,field2")
|
||||
os.Setenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ENABLED", "true")
|
||||
|
@ -1124,6 +1125,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__REDIRECT_BASE_URL")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__USERNAME_FIELD")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__ROLE_FIELD")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__SCOPES")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__IMPLICIT_ROLES")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__OIDC__CUSTOM_FIELDS")
|
||||
os.Unsetenv("SFTPGO_HTTPD__BINDINGS__2__SECURITY__ENABLED")
|
||||
|
@ -1173,6 +1175,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.Equal(t, 0, bindings[0].HideLoginURL)
|
||||
require.False(t, bindings[0].Security.Enabled)
|
||||
require.Equal(t, 0, bindings[0].ClientIPHeaderDepth)
|
||||
require.Len(t, bindings[0].OIDC.Scopes, 3)
|
||||
require.Equal(t, 8000, bindings[1].Port)
|
||||
require.Equal(t, "127.0.0.1", bindings[1].Address)
|
||||
require.False(t, bindings[1].EnableHTTPS)
|
||||
|
@ -1183,6 +1186,7 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.Nil(t, bindings[1].TLSCipherSuites)
|
||||
require.Equal(t, 1, bindings[1].HideLoginURL)
|
||||
require.Empty(t, bindings[1].OIDC.ClientID)
|
||||
require.Len(t, bindings[1].OIDC.Scopes, 3)
|
||||
require.False(t, bindings[1].Security.Enabled)
|
||||
require.Equal(t, "Web Admin", bindings[1].Branding.WebAdmin.Name)
|
||||
require.Equal(t, "WebClient", bindings[1].Branding.WebClient.ShortName)
|
||||
|
@ -1213,6 +1217,8 @@ func TestHTTPDBindingsFromEnv(t *testing.T) {
|
|||
require.Equal(t, "redirect base url", bindings[2].OIDC.RedirectBaseURL)
|
||||
require.Equal(t, "preferred_username", bindings[2].OIDC.UsernameField)
|
||||
require.Equal(t, "sftpgo_role", bindings[2].OIDC.RoleField)
|
||||
require.Len(t, bindings[2].OIDC.Scopes, 1)
|
||||
require.Equal(t, "openid", bindings[2].OIDC.Scopes[0])
|
||||
require.True(t, bindings[2].OIDC.ImplicitRoles)
|
||||
require.Len(t, bindings[2].OIDC.CustomFields, 2)
|
||||
require.Equal(t, "field1", bindings[2].OIDC.CustomFields[0])
|
||||
|
|
|
@ -279,6 +279,7 @@ The configuration file contains the following sections:
|
|||
- `client_secret`, string. Defines the application's secret. Default: blank.
|
||||
- `redirect_base_url`, string. Defines the base URL to redirect to after OpenID authentication. The suffix `/web/oidc/redirect` will be added to this base URL, adding also the `web_root` if configured. Default: blank.
|
||||
- `username_field`, string. Defines the ID token claims field to map to the SFTPGo username. Default: blank.
|
||||
- `scopes`, list of strings. Request the OAuth provider to provide the scope information from an authenticated users. The `openid` scope is mandatory. Default: `"openid", "profile", "email"`.
|
||||
- `role_field`, string. Defines the optional ID token claims field to map to a SFTPGo role. If the defined ID token claims field is set to `admin` the authenticated user is mapped to an SFTPGo admin. You don't need to specify this field if you want to use OpenID only for the Web Client UI. Default: blank.
|
||||
- `implicit_roles`, boolean. If set, the `role_field` is ignored and the SFTPGo role is assumed based on the login link used. Default: `false`.
|
||||
- `custom_fields`, list of strings. Custom token claims fields to pass to the pre-login hook. Default: empty.
|
||||
|
|
13
docs/oidc.md
13
docs/oidc.md
|
@ -1,6 +1,7 @@
|
|||
# OpenID Connect
|
||||
|
||||
OpenID Connect integration allows you to map your identity provider users to SFTPGo admins/users and so you can login to SFTPGo Web Client and Web Admin user interfaces using your identity provider.
|
||||
OpenID Connect integration allows you to map your identity provider users to SFTPGo admins/users,
|
||||
so you can login to SFTPGo Web Client and Web Admin user interfaces, using your own identity provider.
|
||||
|
||||
SFTPGo allows to configure per-binding OpenID Connect configurations. The supported configuration parameters are documented within the `oidc` section [here](./full-configuration.md).
|
||||
|
||||
|
@ -42,6 +43,7 @@ Add the following configuration parameters to the SFTPGo configuration file (or
|
|||
"config_url": "http://192.168.1.12:8086/auth/realms/sftpgo",
|
||||
"redirect_base_url": "http://192.168.1.50:8080",
|
||||
"username_field": "preferred_username",
|
||||
"scopes": [ "openid", "profile", "email" ],
|
||||
"role_field": "sftpgo_role",
|
||||
"implicit_roles": false,
|
||||
"custom_fields": []
|
||||
|
@ -104,8 +106,12 @@ And the following is an example ID token which allows the SFTPGo user `user1` to
|
|||
```
|
||||
|
||||
SFTPGo users (not admins) can be created/updated after successful OpenID authentication by defining a [pre-login hook](./dynamic-user-mod.md).
|
||||
You can use the `custom_fields` configuration parameter to define the token claims field names to pass to the pre-login hook, these fields are useful for implementing custom logic when creating/updating the SFTPGo user within the hook.
|
||||
For example you can set the field `sftpgo_home_dir` in your identity provider and add it to the `custom_fields` in the SFTPGo configuration like this:
|
||||
You can use `scopes` configuration to request additional information (claims) about authenticated users (See your provider's own documentation for more information).
|
||||
By default the scopes `"openid", "profile", "email"` are retrieved.
|
||||
The `custom_fields` configuration parameter can be used to define claim field names to pass to the pre-login hook,
|
||||
these fields can be used e.g. for implementing custom logic when creating/updating the SFTPGo user within the hook.
|
||||
For example, if you have created a scope with name `sftpgo` in your identity provider to provide a claim for `sftpgo_home_dir` ,
|
||||
then you can add it to the `custom_fields` in the SFTPGo configuration like this:
|
||||
|
||||
```json
|
||||
...
|
||||
|
@ -115,6 +121,7 @@ For example you can set the field `sftpgo_home_dir` in your identity provider an
|
|||
"config_url": "http://192.168.1.12:8086/auth/realms/sftpgo",
|
||||
"redirect_base_url": "http://192.168.1.50:8080",
|
||||
"username_field": "preferred_username",
|
||||
"scopes": [ "openid", "profile", "email", "sftpgo" ],
|
||||
"role_field": "sftpgo_role",
|
||||
"custom_fields": ["sftpgo_home_dir"]
|
||||
}
|
||||
|
|
4
go.mod
4
go.mod
|
@ -58,7 +58,7 @@ require (
|
|||
github.com/spf13/viper v1.12.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/studio-b12/gowebdav v0.0.0-20220128162035-c7b1ff8a5e62
|
||||
github.com/unrolled/secure v1.11.0
|
||||
github.com/unrolled/secure v1.12.0
|
||||
github.com/wagslane/go-password-validator v0.3.0
|
||||
github.com/xhit/go-simple-mail/v2 v2.11.0
|
||||
github.com/yl2chen/cidranger v1.0.3-0.20210928021809-d1cb2c52f37a
|
||||
|
@ -155,7 +155,7 @@ require (
|
|||
golang.org/x/tools v0.1.11 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220714211235-042d03aeabc9 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220715211116-798f69b842b9 // indirect
|
||||
google.golang.org/grpc v1.48.0 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||
|
|
8
go.sum
8
go.sum
|
@ -760,8 +760,8 @@ github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjM
|
|||
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/unrolled/secure v1.11.0 h1:fjkKhD/MsQnlmz/au+MmFptCFNhvf5iv04ALkdCXRCI=
|
||||
github.com/unrolled/secure v1.11.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/unrolled/secure v1.12.0 h1:7k3jcgLwfjiKkhQde6VbQ3D4KDLtDBqDd/hs3PPANDY=
|
||||
github.com/unrolled/secure v1.12.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=
|
||||
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
|
||||
github.com/xhit/go-simple-mail/v2 v2.11.0 h1:o/056V50zfkO3Mm5tVdo9rG3ryg4ZmJ2XW5GMinHfVs=
|
||||
|
@ -1224,8 +1224,8 @@ google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljW
|
|||
google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA=
|
||||
google.golang.org/genproto v0.0.0-20220714211235-042d03aeabc9 h1:zfXhTgBfGlIh3jMXN06W8qbhFGsh6MJNJiYEuhTddOI=
|
||||
google.golang.org/genproto v0.0.0-20220714211235-042d03aeabc9/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
|
||||
google.golang.org/genproto v0.0.0-20220715211116-798f69b842b9 h1:1aEQRgZ4Gks2SRAkLzIPpIszRazwVfjSFe1cKc+e0Jg=
|
||||
google.golang.org/genproto v0.0.0-20220715211116-798f69b842b9/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
|
|
|
@ -70,6 +70,10 @@ type OIDC struct {
|
|||
// If set, the `RoleField` is ignored and the SFTPGo role is assumed based on
|
||||
// the login link used
|
||||
ImplicitRoles bool `json:"implicit_roles" mapstructure:"implicit_roles"`
|
||||
// Scopes required by the OAuth provider to retrieve information about the authenticated user.
|
||||
// The "openid" scope is required.
|
||||
// Refer to your OAuth provider documentation for more information about this
|
||||
Scopes []string `json:"scopes" mapstructure:"scopes"`
|
||||
// Custom token claims fields to pass to the pre-login hook
|
||||
CustomFields []string `json:"custom_fields" mapstructure:"custom_fields"`
|
||||
provider *oidc.Provider
|
||||
|
@ -116,6 +120,9 @@ func (o *OIDC) initialize() error {
|
|||
if o.RedirectBaseURL == "" {
|
||||
return errors.New("oidc: redirect base URL cannot be empty")
|
||||
}
|
||||
if !util.Contains(o.Scopes, oidc.ScopeOpenID) {
|
||||
return fmt.Errorf("oidc: required scope %q is not set", oidc.ScopeOpenID)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
|
@ -143,7 +150,7 @@ func (o *OIDC) initialize() error {
|
|||
ClientSecret: o.ClientSecret,
|
||||
Endpoint: o.provider.Endpoint(),
|
||||
RedirectURL: o.getRedirectURL(),
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
Scopes: o.Scopes,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -93,6 +93,11 @@ func TestOIDCInitialization(t *testing.T) {
|
|||
RoleField: "sftpgo_role",
|
||||
}
|
||||
err = config.initialize()
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "oidc: required scope \"openid\" is not set")
|
||||
}
|
||||
config.Scopes = []string{oidc.ScopeOpenID}
|
||||
err = config.initialize()
|
||||
if assert.Error(t, err) {
|
||||
assert.Contains(t, err.Error(), "oidc: unable to initialize provider")
|
||||
}
|
||||
|
@ -1263,6 +1268,7 @@ func getTestOIDCServer() *httpdServer {
|
|||
UsernameField: "preferred_username",
|
||||
RoleField: "sftpgo_role",
|
||||
ImplicitRoles: false,
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
CustomFields: nil,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -263,6 +263,11 @@
|
|||
"client_secret": "",
|
||||
"config_url": "",
|
||||
"redirect_base_url": "",
|
||||
"scopes": [
|
||||
"openid",
|
||||
"profile",
|
||||
"email"
|
||||
],
|
||||
"username_field": "",
|
||||
"role_field": "",
|
||||
"implicit_roles": false,
|
||||
|
|
Loading…
Reference in a new issue