Skip to content

add claims and regex policy selector #2248

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

Merged
merged 11 commits into from
Jul 23, 2021
5 changes: 5 additions & 0 deletions changelog/unreleased/claims-policy-selector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Proxy: Add claims policy selector

Using the proxy config file, it is now possible to let let the IdP determine the routing policy by sending an `ocis.routing.policy` claim. Its value will be used to determine the set of routes for the logged in user.

https://github.com/owncloud/ocis/pull/2248
20 changes: 12 additions & 8 deletions ocis-pkg/oidc/claims.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package oidc

const (
Iss = "iss"
Sub = "sub"
Email = "email"
Name = "name"
Iss = "iss"
Sub = "sub"
Email = "email"
Name = "name"
PreferredUsername = "preferred_username"
UIDNumber = "uidnumber"
GIDNumber = "gidnumber"
Groups = "groups"
OwncloudUUID = "ownclouduuid"
UIDNumber = "uidnumber"
GIDNumber = "gidnumber"
Groups = "groups"
OwncloudUUID = "ownclouduuid"
OcisRoutingPolicy = "ocis.routing.policy"
)

// The ProviderMetadata describes an idp.
Expand Down Expand Up @@ -192,4 +193,7 @@ type StandardClaims struct {

// OcisID is a unique, persistent, non reassignable user id
OcisID string `json:"ownclouduuid,omitempty"`

// OcisRoutingPolicy is used to specify the routing policy to use for the ocis proxy
OcisRoutingPolicy string `json:"ocis.routing.policy,omitempty"`
}
5 changes: 3 additions & 2 deletions proxy/config/proxy-example-migration.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"HTTP": {
"Namespace": "com.owncloud"
"http": {
"addr": "0.0.0.0:9200",
"root": "/"
},
"oidc": {
"issuer": "https://localhost:9200",
Expand Down
168 changes: 168 additions & 0 deletions proxy/config/proxy-example-regex.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
{
"http": {
"addr": "0.0.0.0:9200",
"root": "/"
},
"oidc": {
"issuer": "https://localhost:9200",
"insecure": true
},
"policy_selector": {
"regex": {
"selector_cookie_name": "owncloud-selector",
"default_policy": "oc10",
"matches_policies": [
{"priority": 10, "property": "mail", "match": "[email protected]", "policy": "ocis"},
{"priority": 20, "property": "mail", "match": "[^@][email protected]", "policy": "oc10"},
{"priority": 30, "property": "username", "match": "(einstein|feynman)", "policy": "ocis"},
{"priority": 40, "property": "username", "match": ".+", "policy": "oc10"},
{"priority": 50, "property": "id", "match": "4c510ada-c86b-4815-8820-42cdf82c3d51", "policy": "ocis"},
{"priority": 60, "property": "id", "match": "f7fbf8c8-139b-4376-b307-cf0a8c2d0d9c", "policy": "oc10"}
],
"unauthenticated_policy": "oc10"
}
},
"policies": [
{
"name": "ocis",
"routes": [
{
"endpoint": "/",
"backend": "http://localhost:9100"
},
{
"endpoint": "/.well-known/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/konnect/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/signin/",
"backend": "http://localhost:9130"
},
{
"type": "regex",
"endpoint": "/ocs/v[12].php/cloud/(users?|groups)",
"backend": "http://localhost:9110"
},
{
"endpoint": "/ocs/",
"backend": "http://localhost:9140"
},
{
"type": "query",
"endpoint": "/remote.php/?preview=1",
"backend": "http://localhost:9115"
},
{
"endpoint": "/remote.php/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/dav/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/webdav/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/status.php",
"backend": "http://localhost:9140"
},
{
"endpoint": "/index.php/",
"backend": "http://localhost:9140"
},
{
"endpoint": "/data",
"backend": "http://localhost:9140"
},
{
"endpoint": "/graph/",
"backend": "http://localhost:9120"
},
{
"endpoint": "/graph-explorer/",
"backend": "http://localhost:9135"
},
{
"endpoint": "/api/v0/accounts",
"backend": "http://localhost:9181"
},
{
"endpoint": "/accounts.js",
"backend": "http://localhost:9181"
},
{
"endpoint": "/api/v0/settings",
"backend": "http://localhost:9190"
},
{
"endpoint": "/settings.js",
"backend": "http://localhost:9190"
},
{
"endpoint": "/onlyoffice.js",
"backend": "http://localhost:9220"
}
]
},
{
"name": "oc10",
"routes": [
{
"endpoint": "/",
"backend": "http://localhost:9100"
},
{
"endpoint": "/.well-known/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/konnect/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/signin/",
"backend": "http://localhost:9130"
},
{
"endpoint": "/ocs/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/remote.php/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/dav/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/webdav/",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
},
{
"endpoint": "/status.php",
"backend": "https://demo.owncloud.com"
},
{
"endpoint": "/index.php/",
"backend": "https://demo.owncloud.com"
},
{
"endpoint": "/data",
"backend": "https://demo.owncloud.com",
"apache-vhost": true
}
]
}
]
}
5 changes: 3 additions & 2 deletions proxy/config/proxy-example.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"HTTP": {
"Namespace": "com.owncloud"
"http": {
"addr": "0.0.0.0:9200",
"root": "/"
},
"policy_selector": {
"static": {
Expand Down
6 changes: 6 additions & 0 deletions proxy/pkg/command/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ func loadMiddlewares(ctx context.Context, l log.Logger, cfg *config.Config) alic
middleware.AutoprovisionAccounts(cfg.AutoprovisionAccounts),
),

middleware.SelectorCookie(
middleware.Logger(l),
middleware.UserProvider(userProvider),
middleware.PolicySelectorConfig(*cfg.PolicySelector),
),

// finally, trigger home creation when a user logs in
middleware.CreateHome(
middleware.Logger(l),
Expand Down
27 changes: 26 additions & 1 deletion proxy/pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package config

import "context"
import (
"context"
)

// Log defines the available logging configuration.
type Log struct {
Expand Down Expand Up @@ -141,6 +143,8 @@ type OIDC struct {
type PolicySelector struct {
Static *StaticSelectorConf
Migration *MigrationSelectorConf
Claims *ClaimsSelectorConf
Regex *RegexSelectorConf
}

// StaticSelectorConf is the config for the static-policy-selector
Expand All @@ -166,6 +170,27 @@ type MigrationSelectorConf struct {
UnauthenticatedPolicy string `mapstructure:"unauthenticated_policy"`
}

// ClaimsSelectorConf is the config for the claims-selector
type ClaimsSelectorConf struct {
DefaultPolicy string `mapstructure:"default_policy"`
UnauthenticatedPolicy string `mapstructure:"unauthenticated_policy"`
SelectorCookieName string `mapstructure:"selector_cookie_name"`
}

// RegexSelectorConf is the config for the regex-selector
type RegexSelectorConf struct {
DefaultPolicy string `mapstructure:"default_policy"`
MatchesPolicies []RegexRuleConf `mapstructure:"matches_policies"`
UnauthenticatedPolicy string `mapstructure:"unauthenticated_policy"`
SelectorCookieName string `mapstructure:"selector_cookie_name"`
}
type RegexRuleConf struct {
Priority int `mapstructure:"priority"`
Property string `mapstructure:"property"`
Match string `mapstructure:"match"`
Policy string `mapstructure:"policy"`
}

// New initializes a new configuration
func New() *Config {
return &Config{
Expand Down
2 changes: 1 addition & 1 deletion proxy/pkg/flagset/flagset.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
&cli.StringFlag{
Name: "user-cs3-claim",
Value: flags.OverrideDefaultString(cfg.UserCS3Claim, "mail"),
Usage: "The claim to use when looking up a user in the CS3 API, eg. 'userid' or 'mail'",
Usage: "The CS3 claim to use when looking up a user in the CS3 users API, eg. 'userid', 'username' or 'mail'",
EnvVars: []string{"PROXY_USER_CS3_CLAIM"},
Destination: &cfg.UserCS3Claim,
},
Expand Down
16 changes: 12 additions & 4 deletions proxy/pkg/middleware/account_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func AccountResolver(optionSetters ...Option) func(next http.Handler) http.Handl
"expires": int64(60),
})
if err != nil {
logger.Fatal().Err(err).Msgf("Could not initialize token-manager")
logger.Fatal().Err(err).Msg("Could not initialize token-manager")
}

return &accountResolver{
Expand All @@ -53,8 +53,10 @@ type accountResolver struct {

// TODO do not use the context to store values: https://medium.com/@cep21/how-to-correctly-use-context-context-in-go-1-7-8f2c0fafdf39
func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
claims := oidc.FromContext(req.Context())
u, ok := revauser.ContextGetUser(req.Context())
ctx := req.Context()
claims := oidc.FromContext(ctx)
u, ok := revauser.ContextGetUser(ctx)
// TODO what if an X-Access-Token is set? happens eg for download requests to the /data endpoint in the reva frontend

if claims == nil && !ok {
m.next.ServeHTTP(w, req)
Expand Down Expand Up @@ -83,6 +85,8 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
}
m.logger.Debug().Interface("claims", claims).Msg("Autoprovisioning user")
u, err = m.userProvider.CreateUserFromClaims(req.Context(), claims)
// TODO instead of creating an account create a personal storage via the CS3 admin api?
// see https://cs3org.github.io/cs3apis/#cs3.admin.user.v1beta1.CreateUserRequest
}

if errors.Is(err, backend.ErrAccountDisabled) {
Expand All @@ -97,6 +101,10 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}

// add user to context for selectors
ctx = revauser.ContextSetUser(ctx, u)
req = req.WithContext(ctx)

m.logger.Debug().Interface("claims", claims).Interface("user", u).Msg("associated claims with user")
}

Expand All @@ -105,7 +113,7 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) {
m.logger.Error().Err(err).Msg("could not get owner scope")
return
}
token, err := m.tokenManager.MintToken(req.Context(), u, s)
token, err := m.tokenManager.MintToken(ctx, u, s)
if err != nil {
m.logger.Error().Err(err).Msg("could not mint token")
w.WriteHeader(http.StatusInternalServerError)
Expand Down
12 changes: 11 additions & 1 deletion proxy/pkg/middleware/options.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package middleware

import (
"github.com/owncloud/ocis/proxy/pkg/user/backend"
"net/http"
"time"

"github.com/owncloud/ocis/proxy/pkg/user/backend"

settings "github.com/owncloud/ocis/settings/pkg/proto/v0"

gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
Expand All @@ -23,6 +24,8 @@ type Options struct {
Logger log.Logger
// TokenManagerConfig for communicating with the reva token manager
TokenManagerConfig config.TokenManager
// PolicySelectorConfig for using the policy selector
PolicySelector config.PolicySelector
// HTTPClient to use for communication with the oidcAuth provider
HTTPClient *http.Client
// AccountsClient for resolving accounts
Expand Down Expand Up @@ -82,6 +85,13 @@ func TokenManagerConfig(cfg config.TokenManager) Option {
}
}

// PolicySelectorConfig provides a function to set the policy selector config option.
func PolicySelectorConfig(cfg config.PolicySelector) Option {
return func(o *Options) {
o.PolicySelector = cfg
}
}

// HTTPClient provides a function to set the http client config option.
func HTTPClient(c *http.Client) Option {
return func(o *Options) {
Expand Down
Loading