Add id_token in auth token request
ID token carries personal information about end-users that authenticate on an OpenID Connect flow.
This commit is contained in:
67
auth/auth.go
67
auth/auth.go
@@ -12,7 +12,6 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -25,6 +24,7 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/auth/settings"
|
||||
"github.com/cortezaproject/corteza-server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza-server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza-server/pkg/options"
|
||||
"github.com/cortezaproject/corteza-server/pkg/version"
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
"github.com/go-chi/chi/v5"
|
||||
oauth2def "github.com/go-oauth2/oauth2/v4"
|
||||
"github.com/spf13/cast"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
@@ -87,17 +88,10 @@ func New(ctx context.Context, log *zap.Logger, oa2m oauth2def.Manager, s store.S
|
||||
// this is a bit silly and a bad design of the oauth2 server lib
|
||||
// why do we need to keep on load the client??
|
||||
var (
|
||||
clientID uint64
|
||||
client *types.AuthClient
|
||||
client *types.AuthClient
|
||||
)
|
||||
|
||||
clientID, err = strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not authorize client: %w", err)
|
||||
}
|
||||
|
||||
client, err = store.LookupAuthClientByID(ctx, s, clientID)
|
||||
if err != nil {
|
||||
if client, err = clientLookup(ctx, s, id); err != nil {
|
||||
return false, fmt.Errorf("could not authorize client: %w", err)
|
||||
}
|
||||
|
||||
@@ -113,17 +107,10 @@ func New(ctx context.Context, log *zap.Logger, oa2m oauth2def.Manager, s store.S
|
||||
// this is a bit silly and a bad design of the oauth2 server lib
|
||||
// why do we need to keep on load the client??
|
||||
var (
|
||||
clientID uint64
|
||||
client *types.AuthClient
|
||||
client *types.AuthClient
|
||||
)
|
||||
|
||||
clientID, err = strconv.ParseUint(tgr.ClientID, 10, 64)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("could not authorize client: %w", err)
|
||||
}
|
||||
|
||||
client, err = store.LookupAuthClientByID(ctx, s, clientID)
|
||||
if err != nil {
|
||||
if client, err = clientLookup(ctx, s, tgr.ClientID); err != nil {
|
||||
return false, fmt.Errorf("could not authorize client: %w", err)
|
||||
}
|
||||
|
||||
@@ -139,7 +126,7 @@ func New(ctx context.Context, log *zap.Logger, oa2m oauth2def.Manager, s store.S
|
||||
fieldsValue = make(map[string]interface{})
|
||||
handlers.SubSplit(ti, fieldsValue)
|
||||
fieldsValue["refresh_token_expires_in"] = int(ti.GetRefreshExpiresIn() / time.Second)
|
||||
if err = handlers.Profile(ctx, ti, fieldsValue); err != nil {
|
||||
if err = userProfile(ctx, s, ti, fieldsValue); err != nil {
|
||||
log.Error("failed to add profile data", zap.Error(err))
|
||||
}
|
||||
|
||||
@@ -464,3 +451,43 @@ func (svc service) WellKnownOpenIDConfiguration() http.HandlerFunc {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
}
|
||||
|
||||
func clientLookup(ctx context.Context, s store.AuthClients, identifier interface{}) (*types.AuthClient, error) {
|
||||
if id := cast.ToUint64(identifier); id > 0 {
|
||||
return store.LookupAuthClientByID(ctx, s, id)
|
||||
} else if h := cast.ToString(identifier); handle.IsValid(h) {
|
||||
return store.LookupAuthClientByHandle(ctx, s, h)
|
||||
} else {
|
||||
return nil, systemService.AuthClientErrInvalidID()
|
||||
}
|
||||
}
|
||||
|
||||
// Profile fills map with user's data
|
||||
//
|
||||
// If scope supports it (contains "profile") user is loaded and
|
||||
// map is filled with username (handle), email and name
|
||||
func userProfile(ctx context.Context, s store.Users, ti oauth2def.TokenInfo, data map[string]interface{}) error {
|
||||
if !auth.CheckScope(ti.GetScope(), "profile") {
|
||||
return nil
|
||||
}
|
||||
|
||||
userID, _ := auth.ExtractFromSubClaim(ti.GetUserID())
|
||||
if userID == 0 {
|
||||
return fmt.Errorf("invalid user ID in 'sub' claim")
|
||||
}
|
||||
|
||||
user, err := store.LookupUserByID(ctx, s, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data["handle"] = user.Handle
|
||||
data["name"] = user.Name
|
||||
data["email"] = user.Email
|
||||
|
||||
if user.Meta != nil && user.Meta.PreferredLanguage != "" {
|
||||
data["preferred_language"] = user.Meta.PreferredLanguage
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/store"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
@@ -15,8 +16,12 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func (svc clientService) LookupByID(ctx context.Context, clientID uint64) (*types.AuthClient, error) {
|
||||
return store.LookupAuthClientByID(ctx, svc.store, clientID)
|
||||
func (svc clientService) Lookup(ctx context.Context, identifier interface{}) (*types.AuthClient, error) {
|
||||
return clientLookup(ctx, svc.store, identifier)
|
||||
}
|
||||
|
||||
func (svc clientService) LookupByHandle(ctx context.Context, handle string) (*types.AuthClient, error) {
|
||||
return store.LookupAuthClientByHandle(ctx, svc.store, handle)
|
||||
}
|
||||
|
||||
func (svc clientService) Confirmed(ctx context.Context, userID uint64) (types.AuthConfirmedClientSet, error) {
|
||||
|
||||
6
auth/external/goth.go
vendored
6
auth/external/goth.go
vendored
@@ -52,12 +52,12 @@ func SetupGothProviders(log *zap.Logger, redirectUrl string, ep ...settings.Prov
|
||||
|
||||
wellKnown := strings.TrimSuffix(pc.IssuerUrl, "/") + WellKnown
|
||||
|
||||
scope := pc.Scope
|
||||
scope := strings.Split(pc.Scope, " ")
|
||||
if len(scope) == 0 {
|
||||
scope = "email"
|
||||
scope = append(scope, "email")
|
||||
}
|
||||
|
||||
if provider, err = openidConnect.New(pc.Key, pc.Secret, redirect, wellKnown, scope); err != nil {
|
||||
if provider, err = openidConnect.New(pc.Key, pc.Secret, redirect, wellKnown, scope...); err != nil {
|
||||
log.Error("failed to discover OIDC provider", zap.Error(err), zap.String("well-known", wellKnown))
|
||||
continue
|
||||
} else {
|
||||
|
||||
@@ -83,7 +83,7 @@ func (h *AuthHandlers) getAuthorizedClients(req *request.AuthReq) (ss authClient
|
||||
ss = make(authClients, 0, len(set))
|
||||
|
||||
for i := range set {
|
||||
client, err = h.ClientService.LookupByID(req.Context(), set[i].ClientID)
|
||||
client, err = h.ClientService.Lookup(req.Context(), set[i].ClientID)
|
||||
if errors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -78,9 +78,7 @@ func (h AuthHandlers) handleSuccessfulExternalAuth(w http.ResponseWriter, r *htt
|
||||
})(w, r)
|
||||
}
|
||||
|
||||
func (h AuthHandlers) handleFailedExternalAuth(w http.ResponseWriter, r *http.Request, err error) {
|
||||
//provider := chi.URLParam(r, "provider")
|
||||
|
||||
func (h AuthHandlers) handleFailedExternalAuth(w http.ResponseWriter, _ *http.Request, err error) {
|
||||
if strings.Contains(err.Error(), "Error processing your OAuth request: Invalid oauth_verifier parameter") {
|
||||
// Just take user through the same loop again
|
||||
w.Header().Set("Location", GetLinks().Profile)
|
||||
@@ -88,8 +86,8 @@ func (h AuthHandlers) handleFailedExternalAuth(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "SSO Error: %v", err.Error())
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = fmt.Fprintf(w, "SSO Error: %v", err.Error())
|
||||
}
|
||||
|
||||
func beginUserAuth(w http.ResponseWriter, r *http.Request, eh external.ExternalAuthHandler) {
|
||||
|
||||
@@ -11,9 +11,11 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/lestrrat-go/jwx/jwa"
|
||||
"github.com/lestrrat-go/jwx/jwk"
|
||||
|
||||
"github.com/go-chi/jwtauth"
|
||||
oauth2errors "github.com/go-oauth2/oauth2/v4/errors"
|
||||
"github.com/lestrrat-go/jwx/jwk"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/auth/oauth2"
|
||||
@@ -58,6 +60,9 @@ func (h AuthHandlers) oauth2Authorize(req *request.AuthReq) (err error) {
|
||||
// No client validation is done at this point;
|
||||
// first, see if user is able to authenticate.
|
||||
request.SetOauth2Client(req.Session, client)
|
||||
|
||||
// ensure we're dealing with client ID in case someone used handle
|
||||
req.Request.Form.Set("client_id", strconv.FormatUint(client.ID, 10))
|
||||
}
|
||||
|
||||
// set to -1 to make sure wrapping request handler
|
||||
@@ -161,7 +166,6 @@ func (h AuthHandlers) oauth2Token(req *request.AuthReq) (err error) {
|
||||
req.Status = -1
|
||||
|
||||
client, err := h.loadRequestedClient(req)
|
||||
|
||||
if err != nil {
|
||||
return h.tokenError(req.Response, err)
|
||||
}
|
||||
@@ -318,9 +322,8 @@ func (h AuthHandlers) verifyDefaultClient() error {
|
||||
func (h AuthHandlers) loadRequestedClient(req *request.AuthReq) (client *types.AuthClient, err error) {
|
||||
return client, func() (err error) {
|
||||
var (
|
||||
id string
|
||||
clientID uint64
|
||||
found bool
|
||||
id string
|
||||
found bool
|
||||
)
|
||||
|
||||
if id, _, found = req.Request.BasicAuth(); !found {
|
||||
@@ -331,31 +334,7 @@ func (h AuthHandlers) loadRequestedClient(req *request.AuthReq) (client *types.A
|
||||
}
|
||||
}
|
||||
|
||||
h.Log.Debug("loading client", zap.String("info", id))
|
||||
|
||||
if clientID, err = strconv.ParseUint(id, 10, 64); err != nil {
|
||||
return errors.InvalidData("failed to parse client ID from params: %v", err)
|
||||
|
||||
} else if clientID == 0 {
|
||||
return errors.InvalidData("invalid client ID")
|
||||
}
|
||||
|
||||
if client = request.GetOauth2Client(req.Session); client != nil {
|
||||
h.Log.Debug("client loaded from session", zap.Uint64("ID", client.ID))
|
||||
|
||||
// ensure that session holds the right client and
|
||||
// not some leftover from a previous flow
|
||||
if client.ID != clientID {
|
||||
h.Log.Debug("stale client found in session")
|
||||
|
||||
// cleanup leftovers
|
||||
client = nil
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
client, err = h.ClientService.LookupByID(req.Context(), clientID)
|
||||
client, err = h.ClientService.Lookup(req.Context(), id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid client: %w", err)
|
||||
}
|
||||
@@ -370,6 +349,8 @@ func (h AuthHandlers) handleTokenRequest(req *request.AuthReq, client *types.Aut
|
||||
r = req.Request
|
||||
w = req.Response
|
||||
ctx = req.Context()
|
||||
|
||||
user *types.User
|
||||
)
|
||||
|
||||
req.Status = -1
|
||||
@@ -378,7 +359,7 @@ func (h AuthHandlers) handleTokenRequest(req *request.AuthReq, client *types.Aut
|
||||
return h.tokenError(w, fmt.Errorf("invalid client: %w", err))
|
||||
}
|
||||
|
||||
// add client to context so we can reach it from client store via context.Value() fn
|
||||
// add client to context: we can reach it from client store via context.Value() fn
|
||||
// this way we work around the limitations we have with the oauth2 lib.
|
||||
ctx = context.WithValue(ctx, &oauth2.ContextClientStore{}, client)
|
||||
r = req.Request.Clone(ctx)
|
||||
@@ -388,39 +369,71 @@ func (h AuthHandlers) handleTokenRequest(req *request.AuthReq, client *types.Aut
|
||||
return h.tokenError(w, err)
|
||||
}
|
||||
|
||||
if gt == oauth2def.ClientCredentials {
|
||||
// Authenticated with client credentials!
|
||||
//
|
||||
// We'll use info from client security
|
||||
if client.Security == nil || client.Security.ImpersonateUser == 0 {
|
||||
return h.tokenError(w, errors.Internal("auth client security configuration invalid"))
|
||||
}
|
||||
|
||||
tgr.UserID = strings.Join(append(
|
||||
[]string{fmt.Sprintf("%d", client.Security.ImpersonateUser)},
|
||||
client.Security.ForcedRoles...,
|
||||
), " ")
|
||||
}
|
||||
|
||||
ti, err := h.OAuth2.GetAccessToken(ctx, gt, tgr)
|
||||
if err != nil {
|
||||
return h.tokenError(w, err)
|
||||
}
|
||||
|
||||
suCtx := auth.SetIdentityToContext(ctx, auth.ServiceUser())
|
||||
|
||||
switch gt {
|
||||
case oauth2def.ClientCredentials:
|
||||
// Authenticated with client credentials!
|
||||
|
||||
// First, validate client's security settings
|
||||
if client.Security == nil || client.Security.ImpersonateUser == 0 {
|
||||
return h.tokenError(w, errors.Internal("auth client security configuration invalid"))
|
||||
}
|
||||
|
||||
// Load the user
|
||||
if user, err = h.UserService.FindByAny(suCtx, client.Security.ImpersonateUser); err != nil {
|
||||
return h.tokenError(w, fmt.Errorf("could not generate token for impersonated user: %v", err))
|
||||
}
|
||||
|
||||
case oauth2def.AuthorizationCode, oauth2def.Refreshing:
|
||||
userID := ti.GetUserID()
|
||||
if i := strings.Index(ti.GetUserID(), " "); i > 0 {
|
||||
// userID field from the token could contain encoded roles
|
||||
// @todo investigate if role-encoding into user-id field is still needed?
|
||||
userID = userID[:i]
|
||||
}
|
||||
|
||||
if user, err = h.UserService.FindByAny(suCtx, userID); err != nil {
|
||||
return h.tokenError(w, fmt.Errorf("could not generate token: %v", err))
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported oauth2 grant type: %v", gt)
|
||||
}
|
||||
|
||||
var (
|
||||
user = req.AuthUser.User.Clone()
|
||||
signed []byte
|
||||
scope = strings.Split(ti.GetScope(), " ")
|
||||
)
|
||||
|
||||
signed, err = auth.JWT().Sign(ti.GetAccess(), user, client.ID, strings.Split(ti.GetScope(), " ")...)
|
||||
signed, err = auth.JWT().Sign(ti.GetAccess(), user, client.ID, scope...)
|
||||
if err != nil {
|
||||
return h.tokenError(w, err)
|
||||
}
|
||||
|
||||
ti.SetAccess(string(signed))
|
||||
|
||||
return token(w, h.OAuth2.GetTokenData(ti), nil)
|
||||
response := h.OAuth2.GetTokenData(ti)
|
||||
|
||||
if strings.Contains(client.Scope, "openid") {
|
||||
var idToken []byte
|
||||
if idToken, err = generateIdToken(user, client, ti, h.Opt.BaseURL); err != nil {
|
||||
return h.tokenError(w, err)
|
||||
}
|
||||
response["id_token"] = string(idToken)
|
||||
}
|
||||
|
||||
return writeResponse(w, response, nil)
|
||||
}
|
||||
|
||||
func (h AuthHandlers) tokenError(w http.ResponseWriter, err error) error {
|
||||
data, statusCode, header := h.OAuth2.GetErrorData(err)
|
||||
return token(w, data, header, statusCode)
|
||||
return writeResponse(w, data, header, statusCode)
|
||||
}
|
||||
|
||||
func (h AuthHandlers) oauth2PublicKeys(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -468,43 +481,38 @@ func SubSplit(ti oauth2def.TokenInfo, data map[string]interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Profile fills map with user's data
|
||||
//
|
||||
// If scope supports it (contains "profile") user is loaded and
|
||||
// map is filled with username (handle), email and name
|
||||
func Profile(ctx context.Context, ti oauth2def.TokenInfo, data map[string]interface{}) error {
|
||||
if !auth.CheckScope(ti.GetScope(), "profile") {
|
||||
return nil
|
||||
// Generates ID token that is part of OIDC flow for doing corteza-to-corteza auth
|
||||
func generateIdToken(user *types.User, client *types.AuthClient, ti oauth2def.TokenInfo, baseURL string) (_ []byte, err error) {
|
||||
token := jwt.New()
|
||||
if err = token.Set(jwt.IssuerKey, baseURL); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userID, roles := auth.ExtractFromSubClaim(ti.GetUserID())
|
||||
if userID == 0 {
|
||||
return fmt.Errorf("invalid user ID in 'sub' claim")
|
||||
// we do not know what the admin used for client key value
|
||||
// on the receiving end, so we'll encode both,
|
||||
// client's ID, and it's handle
|
||||
aud := []string{strconv.FormatUint(client.ID, 10)}
|
||||
if len(client.Handle) > 0 {
|
||||
aud = append(aud, client.Handle)
|
||||
}
|
||||
|
||||
user, err := systemService.DefaultUser.FindByID(
|
||||
// inject ad-hoc identity into context so that user service is aware who is
|
||||
// doing the lookup
|
||||
auth.SetIdentityToContext(ctx, auth.Authenticated(userID, roles...)),
|
||||
userID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
if err = token.Set("aud", aud); err != nil {
|
||||
return
|
||||
}
|
||||
if err = token.Set("user_id", strconv.FormatUint(user.ID, 10)); err != nil {
|
||||
return
|
||||
}
|
||||
if err = token.Set("email", user.Email); err != nil {
|
||||
return
|
||||
}
|
||||
if err = token.Set(jwt.ExpirationKey, now().Add(ti.GetAccessExpiresIn()).Unix()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data["handle"] = user.Handle
|
||||
data["name"] = user.Name
|
||||
data["email"] = user.Email
|
||||
|
||||
if user.Meta != nil && user.Meta.PreferredLanguage != "" {
|
||||
data["preferred_language"] = user.Meta.PreferredLanguage
|
||||
}
|
||||
|
||||
return nil
|
||||
return jwt.Sign(token, jwa.HS512, []byte(client.Secret))
|
||||
}
|
||||
|
||||
func token(w http.ResponseWriter, data map[string]interface{}, header http.Header, statusCode ...int) error {
|
||||
func writeResponse(w http.ResponseWriter, data map[string]interface{}, header http.Header, statusCode ...int) error {
|
||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
w.Header().Set("Pragma", "no-cache")
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
"github.com/cortezaproject/corteza-server/auth/request"
|
||||
"github.com/cortezaproject/corteza-server/auth/settings"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
oauth2models "github.com/go-oauth2/oauth2/v4/models"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -125,3 +127,21 @@ func Test_oauth2AuthorizeSuccessSetParams(t *testing.T) {
|
||||
rq.Equal(-1, authReq.Status)
|
||||
rq.Equal(nil, authReq.Data["error"])
|
||||
}
|
||||
|
||||
func Test_generateIdToken(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
signed, err := generateIdToken(
|
||||
&types.User{},
|
||||
&types.AuthClient{
|
||||
Secret: "correct horse battery stable",
|
||||
},
|
||||
&oauth2models.Token{},
|
||||
"http://cortezaproject.org",
|
||||
)
|
||||
|
||||
req.NoError(err)
|
||||
req.NotEmpty(signed)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/auth/external"
|
||||
"github.com/cortezaproject/corteza-server/auth/request"
|
||||
@@ -51,11 +52,12 @@ type (
|
||||
}
|
||||
|
||||
userService interface {
|
||||
FindByAny(ctx context.Context, identifier interface{}) (*types.User, error)
|
||||
Update(context.Context, *types.User) (*types.User, error)
|
||||
}
|
||||
|
||||
clientService interface {
|
||||
LookupByID(context.Context, uint64) (*types.AuthClient, error)
|
||||
Lookup(context.Context, interface{}) (*types.AuthClient, error)
|
||||
Confirmed(context.Context, uint64) (types.AuthConfirmedClientSet, error)
|
||||
Revoke(ctx context.Context, userID, clientID uint64) error
|
||||
}
|
||||
@@ -136,6 +138,14 @@ const (
|
||||
TmplInternalError = "error-internal.html.tpl"
|
||||
)
|
||||
|
||||
var (
|
||||
// wrapper around time.Now() that will aid service testing
|
||||
now = func() *time.Time {
|
||||
c := time.Now()
|
||||
return &c
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
gob.Register(&types.User{})
|
||||
gob.Register(&types.AuthClient{})
|
||||
|
||||
@@ -19,8 +19,7 @@ var _ oauth2.ClientStore = &ContextClientStore{}
|
||||
//
|
||||
// This requires that client is put in context before oauth2 procedures are executed!
|
||||
func (s ContextClientStore) GetByID(ctx context.Context, id string) (oauth2.ClientInfo, error) {
|
||||
return &clientInfo{&types.AuthClient{}}, nil
|
||||
//return &clientInfo{ctx.Value(&ContextClientStore{}).(*types.AuthClient)}, nil
|
||||
return &clientInfo{ctx.Value(&ContextClientStore{}).(*types.AuthClient)}, nil
|
||||
}
|
||||
|
||||
type (
|
||||
|
||||
@@ -65,36 +65,6 @@ func (c CortezaTokenStore) Create(ctx context.Context, info oauth2.TokenInfo) (e
|
||||
return
|
||||
}
|
||||
|
||||
// this is oauth2 specific go-code and there is no
|
||||
// need for it to be moved to MakeAuthStructs fn in auth pkg
|
||||
if code := info.GetCode(); code != "" {
|
||||
oa2t.Code = code
|
||||
} else {
|
||||
// When creating non-access-code tokens,
|
||||
// we need to overwrite expiration time
|
||||
// with custom values for access or refresh token
|
||||
oa2t.Access = info.GetAccess()
|
||||
oa2t.ExpiresAt = info.GetAccessCreateAt().Add(info.GetAccessExpiresIn())
|
||||
|
||||
if refresh := info.GetRefresh(); refresh != "" {
|
||||
oa2t.Refresh = info.GetRefresh()
|
||||
oa2t.ExpiresAt = info.GetRefreshCreateAt().Add(info.GetRefreshExpiresIn())
|
||||
}
|
||||
}
|
||||
|
||||
//if oa2t.ClientID, err = strconv.ParseUint(info.GetClientID(), 10, 64); err != nil {
|
||||
// return fmt.Errorf("could not parse client ID from token info: %w", err)
|
||||
//}
|
||||
|
||||
//if info.GetUserID() != "" {
|
||||
// if oa2t.UserID, _ = auth.ExtractFromSubClaim(info.GetUserID()); oa2t.UserID == 0 {
|
||||
// // UserID stores collection of IDs: user's ID and set of all roles user is member of
|
||||
// return fmt.Errorf("could not parse user ID from token info")
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//// copy user id to auth client confirmation
|
||||
//acc.UserID = oa2t.UserID
|
||||
if err = store.UpsertAuthConfirmedClient(ctx, c.Store, acc); err != nil {
|
||||
return
|
||||
}
|
||||
@@ -169,7 +139,7 @@ func (c CortezaTokenStore) GetByRefresh(ctx context.Context, refresh string) (oa
|
||||
return internal, t.Data.Unmarshal(internal)
|
||||
}
|
||||
|
||||
func makeAuthStructs(ctx context.Context, jwtID, userID, clientID uint64, data oauth2.TokenInfo, expiresAt time.Duration) (oa2t *types.AuthOa2token, acc *types.AuthConfirmedClient, err error) {
|
||||
func makeAuthStructs(ctx context.Context, jwtID, userID, clientID uint64, info oauth2.TokenInfo, expiresAt time.Duration) (oa2t *types.AuthOa2token, acc *types.AuthConfirmedClient, err error) {
|
||||
var (
|
||||
eti = auth.GetExtraReqInfoFromContext(ctx)
|
||||
createdAt = time.Now().Round(time.Second)
|
||||
@@ -191,9 +161,24 @@ func makeAuthStructs(ctx context.Context, jwtID, userID, clientID uint64, data o
|
||||
ConfirmedAt: createdAt,
|
||||
}
|
||||
|
||||
if oa2t.Data, err = json.Marshal(data); err != nil {
|
||||
if oa2t.Data, err = json.Marshal(info); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if code := info.GetCode(); code != "" {
|
||||
oa2t.Code = code
|
||||
} else {
|
||||
// When creating non-access-code tokens,
|
||||
// we need to overwrite expiration time
|
||||
// with custom values for access or refresh token
|
||||
oa2t.Access = info.GetAccess()
|
||||
oa2t.ExpiresAt = info.GetAccessCreateAt().Add(info.GetAccessExpiresIn())
|
||||
|
||||
if refresh := info.GetRefresh(); refresh != "" {
|
||||
oa2t.Refresh = info.GetRefresh()
|
||||
oa2t.ExpiresAt = info.GetRefreshCreateAt().Add(info.GetRefreshExpiresIn())
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza-server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza-server/pkg/options"
|
||||
"github.com/cortezaproject/corteza-server/pkg/payload"
|
||||
"github.com/go-oauth2/oauth2/v4"
|
||||
"github.com/go-oauth2/oauth2/v4/errors"
|
||||
"github.com/go-oauth2/oauth2/v4/manage"
|
||||
@@ -73,8 +77,6 @@ func NewServer(manager oauth2.Manager) *server.Server {
|
||||
AllowedGrantTypes: []oauth2.GrantType{
|
||||
oauth2.AuthorizationCode,
|
||||
oauth2.Refreshing,
|
||||
// before enabling ClientCredentials grant type, we need to know how to modify released token
|
||||
// using client's security info; how to enforce impersonated user and his roles.
|
||||
oauth2.ClientCredentials,
|
||||
},
|
||||
AllowedCodeChallengeMethods: []oauth2.CodeChallengeMethod{
|
||||
@@ -83,6 +85,32 @@ func NewServer(manager oauth2.Manager) *server.Server {
|
||||
},
|
||||
}, manager)
|
||||
|
||||
srv.ClientInfoHandler = func(r *http.Request) (clientID, clientSecret string, err error) {
|
||||
// check in basic handler first
|
||||
clientID, clientSecret, err = server.ClientBasicHandler(r)
|
||||
|
||||
if clientID == "" && clientSecret == "" {
|
||||
//error or no error, if ID & secret are empty,
|
||||
// check the form handler
|
||||
clientID, clientSecret, err = server.ClientFormHandler(r)
|
||||
}
|
||||
|
||||
// just in case, when client's handle is used instead of the ID
|
||||
// preload it here
|
||||
if id := payload.ParseUint64(clientID); id == 0 && handle.IsValid(clientID) {
|
||||
var client oauth2.ClientInfo
|
||||
client, err = manager.GetClient(r.Context(), clientID)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("could not resolve client info: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
clientID = client.GetID()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
|
||||
return errors.NewResponse(err, 500)
|
||||
})
|
||||
|
||||
@@ -199,12 +199,6 @@ func (svc user) FindByHandle(ctx context.Context, handle string) (u *types.User,
|
||||
}
|
||||
|
||||
// FindByAny finds user by given identifier (context, id, handle, email)
|
||||
//
|
||||
// This function goes against the context anti (!!!) pattern we're using
|
||||
// (and trying to get rid of)
|
||||
//
|
||||
// Main reason to push ctx here as the 1st arg is allow (simple) interface definition
|
||||
// in the consumers that reside under the pkg/
|
||||
func (svc user) FindByAny(ctx context.Context, identifier interface{}) (u *types.User, err error) {
|
||||
if ctx, ok := identifier.(context.Context); ok {
|
||||
identifier = internalAuth.GetIdentityFromContext(ctx).Identity()
|
||||
|
||||
Reference in New Issue
Block a user