Skip to content
Snippets Groups Projects
Verified Commit 17001cb7 authored by Gabriel Zachmann's avatar Gabriel Zachmann
Browse files

make consent screen of web interface skippable by adding list of trusted_redirect_uris

parent 863bf336
No related branches found
No related tags found
No related merge requests found
......@@ -101,6 +101,12 @@ logging:
# URL with documentation about the service
service_documentation: "https://mytoken-docs.data.kit.edu/"
# A list of trusted redirect uris. If the client type is web and redirect_uri matches on of the following regexes the
# consent screen will be skipped. Do this only for applications with well-known redirect-uris and high trust. But it
# makes sense to add your own web interface.
trusted_redirect_uris:
#- "^(https:\\/\\/mytoken\\.example\\.com)?\\/"
# Configuration and enabling/disabling for different features
features:
# The supported oidc flows
......@@ -109,7 +115,7 @@ features:
# Specify restriction keys to disable support for them; on default all restriction keys are supported.
unsupported_restrictions:
# - nbf
# - nbf
# - exp
# - scope
# - audience
......
......@@ -80,6 +80,12 @@ logging:
# URL with documentation about the service
service_documentation: "https://mytoken-docs.data.kit.edu/"
# A list of trusted redirect uris. If the client type is web and redirect_uri matches on of the following regexes the
# consent screen will be skipped. Do this only for applications with well-known redirect-uris and high trust. But it
# makes sense to add your own web interface.
trusted_redirect_uris:
#- "^(https:\\/\\/mytoken\\.example\\.com)?\\/"
# Configuration and enabling/disabling for different features
features:
# The supported oidc flows
......@@ -88,7 +94,7 @@ features:
# Specify restriction keys to disable support for them; on default all restriction keys are supported.
unsupported_restrictions:
# - nbf
# - nbf
# - exp
# - scope
# - audience
......
......@@ -3,6 +3,7 @@ package config
import (
"io/ioutil"
"net/url"
"regexp"
"strings"
"github.com/coreos/go-oidc/v3/oidc"
......@@ -102,19 +103,21 @@ var defaultConfig = Config{
// Config holds the server configuration
type Config struct {
IssuerURL string `yaml:"issuer"`
Host string // Extracted from the IssuerURL
Server serverConf `yaml:"server"`
GeoIPDBFile string `yaml:"geo_ip_db_file"`
API apiConf `yaml:"api"`
DB DBConf `yaml:"database"`
Signing signingConf `yaml:"signing"`
Logging loggingConf `yaml:"logging"`
ServiceDocumentation string `yaml:"service_documentation"`
Features featuresConf `yaml:"features"`
Providers []*ProviderConf `yaml:"providers"`
ProviderByIssuer map[string]*ProviderConf `yaml:"-"`
ServiceOperator ServiceOperatorConf `yaml:"service_operator"`
IssuerURL string `yaml:"issuer"`
Host string // Extracted from the IssuerURL
Server serverConf `yaml:"server"`
GeoIPDBFile string `yaml:"geo_ip_db_file"`
API apiConf `yaml:"api"`
DB DBConf `yaml:"database"`
Signing signingConf `yaml:"signing"`
Logging loggingConf `yaml:"logging"`
ServiceDocumentation string `yaml:"service_documentation"`
Features featuresConf `yaml:"features"`
TrustedRedirectURIs []string `yaml:"trusted_redirect_uris"`
TrustedRedirectsRegex []*regexp.Regexp `yaml:"-"`
Providers []*ProviderConf `yaml:"providers"`
ProviderByIssuer map[string]*ProviderConf `yaml:"-"`
ServiceOperator ServiceOperatorConf `yaml:"service_operator"`
}
type apiConf struct {
......@@ -343,6 +346,13 @@ func validate() error {
if err = conf.ServiceOperator.validate(); err != nil {
return err
}
for _, r := range conf.TrustedRedirectURIs {
reg, err := regexp.Compile(r)
if err != nil {
return errors.Errorf("invalid config: invalid regex in truested_redirect_uris: '%s'", r)
}
conf.TrustedRedirectsRegex = append(conf.TrustedRedirectsRegex, reg)
}
if len(conf.Providers) <= 0 {
return errors.New("invalid config: providers must have at least one entry")
}
......
......@@ -14,7 +14,6 @@ import (
"github.com/oidc-mytoken/api/v0"
"github.com/oidc-mytoken/server/internal/db"
"github.com/oidc-mytoken/server/internal/oidc/pkce"
"github.com/oidc-mytoken/server/internal/server/httpStatus"
"github.com/oidc-mytoken/server/internal/utils/errorfmt"
"github.com/oidc-mytoken/server/internal/utils/logger"
......@@ -106,38 +105,36 @@ func handleConsentDecline(ctx *fiber.Ctx, authInfo *authcodeinforepo.AuthFlowInf
}.Send(ctx)
}
// HandleConsentPost handles consent confirmation requests
func HandleConsentPost(ctx *fiber.Ctx) error {
rlog := logger.GetRequestLogger(ctx)
authInfo, oState, err := getAuthInfoFromConsentCodeStr(rlog, ctx.Params("consent_code"))
if err != nil {
// Don't log error here, it was already logged
return err
}
if len(ctx.Body()) == 0 {
return handleConsentDecline(ctx, authInfo, oState)
}
req := pkg.ConsentPostRequest{}
if err = json.Unmarshal(ctx.Body(), &req); err != nil {
return model.ErrorToBadRequestErrorResponse(err).Send(ctx)
}
// HandleConsentAccept handles the acceptance of a consent code
func HandleConsentAccept(
rlog log.Ext1FieldLogger, req *pkg.ConsentPostRequest,
oState *state.State,
) *model.Response {
for _, c := range req.Capabilities {
if !api.AllCapabilities.Has(c) {
return model.Response{
return &model.Response{
Status: fiber.StatusBadRequest,
Response: model2.BadRequestError(fmt.Sprintf("unknown capability '%s'", c)),
}.Send(ctx)
}
}
}
for _, c := range req.SubtokenCapabilities {
if !api.AllCapabilities.Has(c) {
return model.Response{
return &model.Response{
Status: fiber.StatusBadRequest,
Response: model2.BadRequestError(fmt.Sprintf("unknown subtoken_capability '%s'", c)),
}.Send(ctx)
}
}
}
provider, ok := config.Get().ProviderByIssuer[req.Issuer]
if !ok {
return &model.Response{
Status: fiber.StatusBadRequest,
Response: api.ErrorUnknownIssuer,
}
}
pkceCode := pkce.NewS256PKCE(utils.RandASCIIString(44))
var authURI string
var err error
if err = db.Transact(
rlog, func(tx *sqlx.Tx) error {
if err = authcodeinforepo.UpdateTokenInfoByState(
......@@ -146,25 +143,35 @@ func HandleConsentPost(ctx *fiber.Ctx) error {
); err != nil {
return err
}
return authcodeinforepo.SetCodeVerifier(rlog, tx, oState, pkceCode.Verifier())
authURI, err = authcode.GetAuthorizationURL(rlog, tx, provider, oState, req.Restrictions)
return err
},
); err != nil {
rlog.Errorf("%s", errorfmt.Full(err))
return model.ErrorToInternalServerErrorResponse(err).Send(ctx)
}
provider, ok := config.Get().ProviderByIssuer[req.Issuer]
if !ok {
return model.Response{
Status: fiber.StatusBadRequest,
Response: api.ErrorUnknownIssuer,
}.Send(ctx)
return model.ErrorToInternalServerErrorResponse(err)
}
pkceChallenge, _ := pkceCode.Challenge()
authURI := authcode.GetAuthorizationURL(rlog, provider, oState.State(), pkceChallenge, req.Restrictions)
return model.Response{
return &model.Response{
Status: httpStatus.StatusOKForward,
Response: map[string]string{
"authorization_uri": authURI,
},
}.Send(ctx)
}
}
// HandleConsentPost handles consent confirmation requests
func HandleConsentPost(ctx *fiber.Ctx) error {
rlog := logger.GetRequestLogger(ctx)
authInfo, oState, err := getAuthInfoFromConsentCodeStr(rlog, ctx.Params("consent_code"))
if err != nil {
// Don't log error here, it was already logged
return err
}
if len(ctx.Body()) == 0 {
return handleConsentDecline(ctx, authInfo, oState)
}
req := pkg.ConsentPostRequest{}
if err = json.Unmarshal(ctx.Body(), &req); err != nil {
return model.ErrorToBadRequestErrorResponse(err).Send(ctx)
}
return HandleConsentAccept(rlog, &req, oState).Send(ctx)
}
......@@ -23,6 +23,7 @@ import (
"github.com/oidc-mytoken/server/internal/model"
"github.com/oidc-mytoken/server/internal/oidc/issuer"
"github.com/oidc-mytoken/server/internal/oidc/pkce"
"github.com/oidc-mytoken/server/internal/server/httpStatus"
"github.com/oidc-mytoken/server/internal/server/routes"
"github.com/oidc-mytoken/server/internal/utils/cookies"
"github.com/oidc-mytoken/server/internal/utils/errorfmt"
......@@ -50,10 +51,19 @@ func Init() {
// GetAuthorizationURL creates a authorization url
func GetAuthorizationURL(
rlog log.Ext1FieldLogger, provider *config.ProviderConf, oState, pkceChallenge string,
rlog log.Ext1FieldLogger, tx *sqlx.Tx, provider *config.ProviderConf, oState *state.State,
restrictions restrictions.Restrictions,
) string {
) (string, error) {
rlog.Debug("Generating authorization url")
pkceCode := pkce.NewS256PKCE(utils.RandASCIIString(44))
if err := db.RunWithinTransaction(
rlog, tx, func(tx *sqlx.Tx) error {
return authcodeinforepo.SetCodeVerifier(rlog, tx, oState, pkceCode.Verifier())
},
); err != nil {
return "", err
}
pkceChallenge, _ := pkceCode.Challenge()
scopes := restrictions.GetScopes()
if len(scopes) <= 0 {
scopes = provider.Scopes
......@@ -87,7 +97,16 @@ func GetAuthorizationURL(
)
}
return oauth2Config.AuthCodeURL(oState, additionalParams...)
return oauth2Config.AuthCodeURL(oState.State(), additionalParams...), nil
}
func trustedRedirectURI(redirectUri string) bool {
for _, r := range config.Get().TrustedRedirectsRegex {
if r.MatchString(redirectUri) {
return true
}
}
return false
}
// StartAuthCodeFlow starts an authorization code flow
......@@ -95,6 +114,16 @@ func StartAuthCodeFlow(ctx *fiber.Ctx, oidcReq *response.OIDCFlowRequest) *model
rlog := logger.GetRequestLogger(ctx)
rlog.Debug("Handle authcode")
req := oidcReq.ToAuthCodeFlowRequest()
native := req.Native() && config.Get().Features.Polling.Enabled
if !native && req.RedirectURI == "" {
return &model.Response{
Status: fiber.StatusBadRequest,
Response: api.Error{
Error: api.ErrorStrInvalidRequest,
ErrorDescription: "parameter redirect_uri must be given for client_type=web",
},
}
}
req.Restrictions.ReplaceThisIp(ctx.IP())
req.Restrictions.ClearUnsupportedKeys()
provider, ok := config.Get().ProviderByIssuer[req.Issuer]
......@@ -123,7 +152,7 @@ func StartAuthCodeFlow(ctx *fiber.Ctx, oidcReq *response.OIDCFlowRequest) *model
res := api.AuthCodeFlowResponse{
ConsentURI: utils.CombineURLPath(consentEndpoint, consentCode.String()),
}
if req.Native() && config.Get().Features.Polling.Enabled {
if native {
poll := authFlowInfo.State.PollingCode(rlog)
authFlowInfo.PollingCode = transfercoderepo.CreatePollingCode(poll, req.ResponseType, req.MaxTokenLen)
res.PollingInfo = api.PollingInfo{
......@@ -136,6 +165,22 @@ func StartAuthCodeFlow(ctx *fiber.Ctx, oidcReq *response.OIDCFlowRequest) *model
rlog.Errorf("%s", errorfmt.Full(err))
return model.ErrorToInternalServerErrorResponse(err)
}
if !native && trustedRedirectURI(req.RedirectURI) {
authURI, err := GetAuthorizationURL(
rlog, nil, provider, state.NewState(consentCode.GetState()),
req.Restrictions,
)
if err != nil {
rlog.Errorf("%s", errorfmt.Full(err))
return model.ErrorToInternalServerErrorResponse(err)
}
return &model.Response{
Status: httpStatus.StatusOKForward,
Response: map[string]string{
"authorization_uri": authURI,
},
}
}
return &model.Response{
Status: fiber.StatusOK,
Response: res,
......
......@@ -37,7 +37,17 @@ $('#login-form').on('submit', function(e){
url: storageGet("mytoken_endpoint"),
data: data,
success: function (res) {
window.location.href = res['consent_uri'];
let consent_uri = res['consent_uri'];
let auth_uri = res['authorization_uri'];
let uri;
if (consent_uri !== undefined) {
uri = consent_uri;
} else if (auth_uri !== undefined) {
uri = auth_uri;
} else {
console.error("Unexpected response: ", res);
}
window.location.href = uri;
},
dataType: "json",
contentType : "application/json"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment