Skip to content
Snippets Groups Projects
Commit 3e3a3a1a authored by Gabriel Zachmann's avatar Gabriel Zachmann
Browse files

improve flow of consent

parent 267baab8
No related branches found
No related tags found
No related merge requests found
......@@ -29,7 +29,6 @@ type AuthFlowInfoOut struct {
SubtokenCapabilities capabilities.Capabilities
Name string
PollingCode bool
AuthorizationURL string
}
type authFlowInfo struct {
......@@ -41,7 +40,6 @@ type authFlowInfo struct {
Name sql.NullString
PollingCode db.BitBool `db:"polling_code"`
ExpiresIn int64 `db:"expires_in"`
AuthorizationURL string `db:"auth_url"`
}
func (i *AuthFlowInfo) toAuthFlowInfo() *authFlowInfo {
......@@ -54,7 +52,6 @@ func (i *AuthFlowInfo) toAuthFlowInfo() *authFlowInfo {
Name: db.NewNullString(i.Name),
ExpiresIn: config.Get().Features.Polling.PollingCodeExpiresAfter,
PollingCode: i.PollingCode != nil,
AuthorizationURL: i.AuthorizationURL,
}
}
......@@ -67,7 +64,6 @@ func (i *authFlowInfo) toAuthFlowInfo() *AuthFlowInfoOut {
SubtokenCapabilities: i.SubtokenCapabilities,
Name: i.Name.String,
PollingCode: bool(i.PollingCode),
AuthorizationURL: i.AuthorizationURL,
}
}
......@@ -81,7 +77,7 @@ func (i *AuthFlowInfo) Store(tx *sqlx.Tx) error {
return err
}
}
_, err := tx.NamedExec(`INSERT INTO AuthInfo (state_h, iss, restrictions, capabilities, subtoken_capabilities, name, expires_in, polling_code, auth_url) VALUES(:state_h, :iss, :restrictions, :capabilities, :subtoken_capabilities, :name, :expires_in, :polling_code, :auth_url)`, store)
_, err := tx.NamedExec(`INSERT INTO AuthInfo (state_h, iss, restrictions, capabilities, subtoken_capabilities, name, expires_in, polling_code) VALUES(:state_h, :iss, :restrictions, :capabilities, :subtoken_capabilities, :name, :expires_in, :polling_code)`, store)
return err
})
}
......@@ -90,7 +86,7 @@ func (i *AuthFlowInfo) Store(tx *sqlx.Tx) error {
func GetAuthFlowInfoByState(state *state.State) (*AuthFlowInfoOut, error) {
info := authFlowInfo{}
if err := db.Transact(func(tx *sqlx.Tx) error {
return tx.Get(&info, `SELECT state_h, iss, restrictions, capabilities, subtoken_capabilities, name, polling_code, auth_url FROM AuthInfo WHERE state_h=? AND expires_at >= CURRENT_TIMESTAMP()`, state)
return tx.Get(&info, `SELECT state_h, iss, restrictions, capabilities, subtoken_capabilities, name, polling_code FROM AuthInfo WHERE state_h=? AND expires_at >= CURRENT_TIMESTAMP()`, state)
}); err != nil {
return nil, err
}
......
......@@ -5,10 +5,10 @@ import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/gofiber/fiber/v2"
"github.com/oidc-mytoken/server/internal/config"
"github.com/oidc-mytoken/server/internal/db/dbrepo/authcodeinforepo"
"github.com/oidc-mytoken/server/internal/db/dbrepo/authcodeinforepo/state"
"github.com/oidc-mytoken/server/internal/endpoints/consent/pkg"
......@@ -16,16 +16,18 @@ import (
"github.com/oidc-mytoken/server/internal/oidc/authcode"
model2 "github.com/oidc-mytoken/server/pkg/model"
"github.com/oidc-mytoken/server/shared/supertoken/capabilities"
"github.com/oidc-mytoken/server/shared/supertoken/restrictions"
)
// handleConsent displays a consent page
func handleConsent(ctx *fiber.Ctx, r restrictions.Restrictions, c capabilities.Capabilities, sc capabilities.Capabilities) error {
func handleConsent(ctx *fiber.Ctx, authInfo *authcodeinforepo.AuthFlowInfoOut) error {
c := authInfo.Capabilities
sc := authInfo.SubtokenCapabilities
binding := map[string]interface{}{
"consent": true,
"empty-navbar": true,
"restrictions": pkg.WebRestrictions{Restrictions: r},
"restrictions": pkg.WebRestrictions{Restrictions: authInfo.Restrictions},
"capabilities": pkg.WebCapabilities(c),
"iss": authInfo.Issuer,
}
if c.Has(capabilities.CapabilityCreateST) {
if len(sc) == 0 {
......@@ -47,22 +49,15 @@ func HandleConsent(ctx *fiber.Ctx) error {
}
return err
}
return handleConsent(ctx, authInfo.Restrictions, authInfo.Capabilities, authInfo.SubtokenCapabilities)
return handleConsent(ctx, authInfo)
}
// HandleConsentPost handles consent confirmation requests
func HandleConsentPost(ctx *fiber.Ctx) error {
consentCode := state.ParseConsentCode(ctx.Params("consent_code"))
oState := state.NewState(consentCode.GetState())
authInfo, err := authcodeinforepo.GetAuthFlowInfoByState(oState)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fiber.ErrNotFound
}
return err
}
authURL := strings.Replace(authInfo.AuthorizationURL, authcode.StatePlaceHolder, oState.State(), 1)
req := pkg.ConsentPostRequest{}
if err = json.Unmarshal(ctx.Body(), &req); err != nil {
if err := json.Unmarshal(ctx.Body(), &req); err != nil {
return model.ErrorToBadRequestErrorResponse(err).Send(ctx)
}
for _, c := range req.Capabilities {
......@@ -81,9 +76,17 @@ func HandleConsentPost(ctx *fiber.Ctx) error {
}.Send(ctx)
}
}
if err = authcodeinforepo.UpdateTokenInfoByState(nil, oState, req.Restrictions, req.Capabilities, req.SubtokenCapabilities); err != nil {
if err := authcodeinforepo.UpdateTokenInfoByState(nil, oState, req.Restrictions, req.Capabilities, req.SubtokenCapabilities); err != nil {
return model.ErrorToInternalServerErrorResponse(err).Send(ctx)
}
provider, ok := config.Get().ProviderByIssuer[req.Issuer]
if !ok {
return model.Response{
Status: fiber.StatusBadRequest,
Response: model2.APIErrorUnknownIssuer,
}.Send(ctx)
}
authURL := authcode.GetAuthorizationURL(provider, oState.State(), req.Restrictions)
return model.Response{
Status: 278,
Response: map[string]string{
......
......@@ -5,7 +5,9 @@ import (
"github.com/oidc-mytoken/server/shared/supertoken/restrictions"
)
// ConsentPostRequest holds the post request for confirming consent
type ConsentPostRequest struct {
Issuer string `json:"oidc_iss"`
Restrictions restrictions.Restrictions `json:"restrictions"`
Capabilities capabilities.Capabilities `json:"capabilities"`
SubtokenCapabilities capabilities.Capabilities `json:"subtoken_capabilities"`
......
......@@ -2,10 +2,6 @@ package pkg
import (
"encoding/json"
"github.com/oidc-mytoken/server/pkg/model"
"github.com/oidc-mytoken/server/shared/supertoken/capabilities"
"github.com/oidc-mytoken/server/shared/supertoken/restrictions"
)
// Redirect types
......@@ -16,24 +12,8 @@ const (
// AuthCodeFlowRequest holds a authorization code flow request
type AuthCodeFlowRequest struct {
Issuer string `json:"oidc_issuer"`
GrantType model.GrantType `json:"grant_type"`
OIDCFlow model.OIDCFlow `json:"oidc_flow"`
Restrictions restrictions.Restrictions `json:"restrictions"`
Capabilities capabilities.Capabilities `json:"capabilities"`
SubtokenCapabilities capabilities.Capabilities `json:"subtoken_capabilities"`
RedirectType string `json:"redirect_type"`
Name string `json:"name"`
ResponseType model.ResponseType `json:"response_type"`
}
// NewAuthCodeFlowRequest creates a new AuthCodeFlowRequest with default values where they can be omitted
func NewAuthCodeFlowRequest() *AuthCodeFlowRequest {
return &AuthCodeFlowRequest{
RedirectType: redirectTypeWeb,
Capabilities: capabilities.Capabilities{capabilities.CapabilityAT},
ResponseType: model.ResponseTypeToken,
}
OIDCFlowRequest
RedirectType string `json:"redirect_type"`
}
// Native checks if the request is native
......@@ -46,14 +26,10 @@ func (r *AuthCodeFlowRequest) Native() bool {
// UnmarshalJSON implements the json unmarshaler interface
func (r *AuthCodeFlowRequest) UnmarshalJSON(data []byte) error {
type authCodeFlowRequest2 AuthCodeFlowRequest
rr := (*authCodeFlowRequest2)(NewAuthCodeFlowRequest())
if err := json.Unmarshal(data, &rr); err != nil {
var tmp OIDCFlowRequest
if err := json.Unmarshal(data, &tmp); err != nil {
return err
}
*r = AuthCodeFlowRequest(*rr)
if r.SubtokenCapabilities != nil && !r.Capabilities.Has(capabilities.CapabilityCreateST) {
r.SubtokenCapabilities = nil
}
*r = tmp.ToAuthCodeFlowRequest()
return nil
}
package pkg
import (
"database/sql/driver"
"encoding/json"
"fmt"
"github.com/oidc-mytoken/server/pkg/model"
"github.com/oidc-mytoken/server/shared/supertoken/capabilities"
"github.com/oidc-mytoken/server/shared/supertoken/restrictions"
)
// OIDCFlowRequest holds the request for an OIDC Flow request
type OIDCFlowRequest struct {
Issuer string `json:"oidc_issuer"`
GrantType model.GrantType `json:"grant_type"`
OIDCFlow model.OIDCFlow `json:"oidc_flow"`
Restrictions restrictions.Restrictions `json:"restrictions"`
Capabilities capabilities.Capabilities `json:"capabilities"`
SubtokenCapabilities capabilities.Capabilities `json:"subtoken_capabilities"`
Name string `json:"name"`
ResponseType model.ResponseType `json:"response_type"`
redirectType string
}
// NewOIDCFlowRequest creates a new OIDCFlowRequest with default values where they can be omitted
func NewOIDCFlowRequest() *OIDCFlowRequest {
return &OIDCFlowRequest{
Capabilities: capabilities.Capabilities{capabilities.CapabilityAT},
ResponseType: model.ResponseTypeToken,
redirectType: redirectTypeWeb,
}
}
// MarshalJSON implements the json.Marshaler interface
func (r OIDCFlowRequest) MarshalJSON() ([]byte, error) {
type ofr OIDCFlowRequest
o := struct {
ofr
RedirectType string `json:"redirect_type"`
}{
ofr: ofr(r),
RedirectType: r.redirectType,
}
return json.Marshal(o)
}
// UnmarshalJSON implements the json.Unmarshaler interface
func (r *OIDCFlowRequest) UnmarshalJSON(data []byte) error {
type ofr OIDCFlowRequest
o := struct {
ofr
RedirectType string `json:"redirect_type"`
}{
ofr: ofr(*NewOIDCFlowRequest()),
}
o.RedirectType = o.redirectType
if err := json.Unmarshal(data, &o); err != nil {
return err
}
o.redirectType = o.RedirectType
*r = OIDCFlowRequest(o.ofr)
if r.SubtokenCapabilities != nil && !r.Capabilities.Has(capabilities.CapabilityCreateST) {
r.SubtokenCapabilities = nil
}
return nil
}
func (r OIDCFlowRequest) ToAuthCodeFlowRequest() AuthCodeFlowRequest {
return AuthCodeFlowRequest{
OIDCFlowRequest: r,
RedirectType: r.redirectType,
}
}
// Scan implements the sql.Scanner interface
func (r *OIDCFlowRequest) Scan(src interface{}) error {
v, ok := src.([]byte)
if !ok {
return fmt.Errorf("bad []byte type assertion")
}
return json.Unmarshal(v, r)
}
// Value implements the driver.Valuer interface
func (r OIDCFlowRequest) Value() (driver.Value, error) {
return json.Marshal(r)
}
package super
import (
"encoding/json"
"github.com/gofiber/fiber/v2"
log "github.com/sirupsen/logrus"
"github.com/oidc-mytoken/server/internal/config"
response "github.com/oidc-mytoken/server/internal/endpoints/token/super/pkg"
"github.com/oidc-mytoken/server/internal/endpoints/token/super/polling"
serverModel "github.com/oidc-mytoken/server/internal/model"
"github.com/oidc-mytoken/server/internal/oidc/authcode"
......@@ -50,10 +53,20 @@ func HandleSuperTokenEndpoint(ctx *fiber.Ctx) error {
}
func handleOIDCFlow(ctx *fiber.Ctx) error {
flow := ctxUtils.GetOIDCFlow(ctx)
switch flow {
req := response.NewOIDCFlowRequest()
if err := json.Unmarshal(ctx.Body(), &req); err != nil {
return serverModel.ErrorToBadRequestErrorResponse(err).Send(ctx)
}
_, ok := config.Get().ProviderByIssuer[req.Issuer]
if !ok {
return serverModel.Response{
Status: fiber.StatusBadRequest,
Response: model.APIErrorUnknownIssuer,
}.Send(ctx)
}
switch req.OIDCFlow {
case model.OIDCFlowAuthorizationCode:
return authcode.StartAuthCodeFlow(ctx).Send(ctx)
return authcode.StartAuthCodeFlow(ctx, *req).Send(ctx)
case model.OIDCFlowDevice:
return serverModel.ResponseNYI.Send(ctx)
default:
......
......@@ -2,7 +2,6 @@ package authcode
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"strings"
......@@ -37,8 +36,6 @@ import (
var redirectURL string
var consentEndpoint string
const StatePlaceHolder = "STATE_PLACEHOLDER"
// Init initializes the authcode component
func Init() {
generalPaths := routes.GetGeneralPaths()
......@@ -46,7 +43,7 @@ func Init() {
consentEndpoint = utils.CombineURLPath(config.Get().IssuerURL, generalPaths.ConsentEndpoint)
}
func authorizationURL(provider *config.ProviderConf, restrictions restrictions.Restrictions) string {
func GetAuthorizationURL(provider *config.ProviderConf, oState string, restrictions restrictions.Restrictions) string {
log.Debug("Generating authorization url")
scopes := restrictions.GetScopes()
if len(scopes) <= 0 {
......@@ -67,20 +64,16 @@ func authorizationURL(provider *config.ProviderConf, restrictions restrictions.R
}
auds := restrictions.GetAudiences()
if len(auds) > 0 {
additionalParams = append(additionalParams, oauth2.SetAuthURLParam("audience", strings.Join(auds, " ")))
additionalParams = append(additionalParams, oauth2.SetAuthURLParam(provider.AudienceRequestParameter, strings.Join(auds, " ")))
}
return oauth2Config.AuthCodeURL(StatePlaceHolder, additionalParams...)
return oauth2Config.AuthCodeURL(oState, additionalParams...)
}
// StartAuthCodeFlow starts an authorization code flow
func StartAuthCodeFlow(ctx *fiber.Ctx) *model.Response {
func StartAuthCodeFlow(ctx *fiber.Ctx, oidcReq response.OIDCFlowRequest) *model.Response {
log.Debug("Handle authcode")
body := ctx.Body()
req := response.NewAuthCodeFlowRequest()
if err := json.Unmarshal(body, &req); err != nil {
return model.ErrorToBadRequestErrorResponse(err)
}
req := oidcReq.ToAuthCodeFlowRequest()
provider, ok := config.Get().ProviderByIssuer[req.Issuer]
if !ok {
return &model.Response{
......@@ -97,7 +90,6 @@ func StartAuthCodeFlow(ctx *fiber.Ctx) *model.Response {
}
req.Restrictions.ReplaceThisIp(ctx.IP())
authURL := authorizationURL(provider, req.Restrictions)
oState, consentCode := state.CreateState(state.Info{Native: req.Native()})
authFlowInfoO := authcodeinforepo.AuthFlowInfoOut{
State: oState,
......@@ -106,7 +98,6 @@ func StartAuthCodeFlow(ctx *fiber.Ctx) *model.Response {
Capabilities: req.Capabilities,
SubtokenCapabilities: req.SubtokenCapabilities,
Name: req.Name,
AuthorizationURL: authURL,
}
authFlowInfo := authcodeinforepo.AuthFlowInfo{
AuthFlowInfoOut: authFlowInfoO,
......
......@@ -81,3 +81,5 @@
<button class="btn btn-primary" role="button" onclick="approve()">Continue</button>
<button class="btn btn-secondary" role="button" onclick="cancel()">Cancel</button>
</div>
<script>const issuer="{{iss}}";</script>
\ No newline at end of file
......@@ -137,6 +137,7 @@ function updateIcons() {
function approve() {
let data = {
"oidc_iss": issuer,
"restrictions": restrictions,
"capabilities": $('.capability-check:checked').map(function(_, el) {
return $(el).val();
......
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