mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-16 12:58:00 +00:00
c347e75485
# Which Problems Are Solved Calls with tokens issued through JWT Profile or Client Credentials Grants were no longer possible and threw a "could not read projectid by clientid (AUTH-GHpw2)" error. ZITADEL checks the allowed origins of an application and load its projectID into the context on any API call. Tokens from service accounts did not contain any clientID and therefore never did that check. But due to a change in https://github.com/zitadel/zitadel/pull/8580, were the service user id was set as client_id in the OIDC session to fix the introspection response (https://github.com/zitadel/zitadel/issues/8590). # How the Problems Are Solved - Check if the project and origin were retrieved and only then check the origins # Additional Changes None. # Additional Context - closes https://github.com/zitadel/zitadel/issues/8676 - relates to https://github.com/zitadel/zitadel/pull/8580 (released on 2.62.0) - relates to https://github.com/zitadel/zitadel/issues/8590
195 lines
5.9 KiB
Go
195 lines
5.9 KiB
Go
//go:generate enumer -type MemberType -trimprefix MemberType
|
|
|
|
package authz
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/zitadel/logging"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/grpc"
|
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
type key int
|
|
|
|
const (
|
|
requestPermissionsKey key = 1
|
|
dataKey key = 2
|
|
allPermissionsKey key = 3
|
|
instanceKey key = 4
|
|
)
|
|
|
|
type CtxData struct {
|
|
UserID string
|
|
OrgID string
|
|
ProjectID string
|
|
AgentID string
|
|
PreferredLanguage string
|
|
ResourceOwner string
|
|
SystemMemberships Memberships
|
|
}
|
|
|
|
func (ctxData CtxData) IsZero() bool {
|
|
return ctxData.UserID == "" || ctxData.OrgID == "" && ctxData.SystemMemberships == nil
|
|
}
|
|
|
|
type Grants []*Grant
|
|
|
|
type Grant struct {
|
|
OrgID string
|
|
Roles []string
|
|
}
|
|
|
|
type Memberships []*Membership
|
|
|
|
type Membership struct {
|
|
MemberType MemberType
|
|
AggregateID string
|
|
//ObjectID differs from aggregate id if object is sub of an aggregate
|
|
ObjectID string
|
|
|
|
Roles []string
|
|
}
|
|
|
|
type MemberType int32
|
|
|
|
const (
|
|
MemberTypeUnspecified MemberType = iota
|
|
MemberTypeOrganization
|
|
MemberTypeProject
|
|
MemberTypeProjectGrant
|
|
MemberTypeIAM
|
|
MemberTypeSystem
|
|
)
|
|
|
|
type TokenVerifier interface {
|
|
ExistsOrg(ctx context.Context, id, domain string) (string, error)
|
|
ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (projectID string, origins []string, err error)
|
|
AccessTokenVerifier
|
|
SystemTokenVerifier
|
|
}
|
|
|
|
type AccessTokenVerifier interface {
|
|
VerifyAccessToken(ctx context.Context, token string) (userID, clientID, agentID, prefLan, resourceOwner string, err error)
|
|
}
|
|
|
|
// AccessTokenVerifierFunc implements the SystemTokenVerifier interface so that a function can be used as a AccessTokenVerifier.
|
|
type AccessTokenVerifierFunc func(context.Context, string) (string, string, string, string, string, error)
|
|
|
|
func (a AccessTokenVerifierFunc) VerifyAccessToken(ctx context.Context, token string) (string, string, string, string, string, error) {
|
|
return a(ctx, token)
|
|
}
|
|
|
|
type SystemTokenVerifier interface {
|
|
VerifySystemToken(ctx context.Context, token string, orgID string) (matchingMemberships Memberships, userID string, err error)
|
|
}
|
|
|
|
// SystemTokenVerifierFunc implements the SystemTokenVerifier interface so that a function can be used as a SystemTokenVerifier.
|
|
type SystemTokenVerifierFunc func(context.Context, string, string) (Memberships, string, error)
|
|
|
|
func (s SystemTokenVerifierFunc) VerifySystemToken(ctx context.Context, token string, orgID string) (Memberships, string, error) {
|
|
return s(ctx, token, orgID)
|
|
}
|
|
|
|
func VerifyTokenAndCreateCtxData(ctx context.Context, token, orgID, orgDomain string, t APITokenVerifier) (_ CtxData, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
tokenWOBearer, err := extractBearerToken(token)
|
|
if err != nil {
|
|
return CtxData{}, err
|
|
}
|
|
userID, clientID, agentID, prefLang, resourceOwner, err := t.VerifyAccessToken(ctx, tokenWOBearer)
|
|
var sysMemberships Memberships
|
|
if err != nil && !zerrors.IsUnauthenticated(err) {
|
|
return CtxData{}, err
|
|
}
|
|
if err != nil {
|
|
logging.WithFields("org_id", orgID, "org_domain", orgDomain).WithError(err).Warn("authz: verify access token")
|
|
var sysTokenErr error
|
|
sysMemberships, userID, sysTokenErr = t.VerifySystemToken(ctx, tokenWOBearer, orgID)
|
|
if sysTokenErr != nil || sysMemberships == nil {
|
|
return CtxData{}, zerrors.ThrowUnauthenticated(errors.Join(err, sysTokenErr), "AUTH-7fs1e", "Errors.Token.Invalid")
|
|
}
|
|
}
|
|
projectID, err := projectIDAndCheckOriginForClientID(ctx, clientID, t)
|
|
if err != nil {
|
|
return CtxData{}, err
|
|
}
|
|
if orgID == "" && orgDomain == "" {
|
|
orgID = resourceOwner
|
|
}
|
|
// System API calls don't have a resource owner
|
|
if orgID != "" {
|
|
orgID, err = t.ExistsOrg(ctx, orgID, orgDomain)
|
|
if err != nil {
|
|
return CtxData{}, zerrors.ThrowPermissionDenied(nil, "AUTH-Bs7Ds", "Organisation doesn't exist")
|
|
}
|
|
}
|
|
return CtxData{
|
|
UserID: userID,
|
|
OrgID: orgID,
|
|
ProjectID: projectID,
|
|
AgentID: agentID,
|
|
PreferredLanguage: prefLang,
|
|
ResourceOwner: resourceOwner,
|
|
SystemMemberships: sysMemberships,
|
|
}, nil
|
|
}
|
|
|
|
func projectIDAndCheckOriginForClientID(ctx context.Context, clientID string, t APITokenVerifier) (string, error) {
|
|
if clientID == "" {
|
|
return "", nil
|
|
}
|
|
projectID, origins, err := t.ProjectIDAndOriginsByClientID(ctx, clientID)
|
|
logging.WithFields("clientID", clientID).OnError(err).Debug("could not check projectID and origin of clientID (might be service account)")
|
|
|
|
// We used to check origins for every token, but service users shouldn't be used publicly (native app / SPA).
|
|
// Therefore, mostly won't send an origin and aren't able to configure them anyway.
|
|
// For the current time we will only check origins for tokens issued to users through apps (code / implicit flow).
|
|
if projectID == "" {
|
|
return "", nil
|
|
}
|
|
return projectID, checkOrigin(ctx, origins)
|
|
}
|
|
|
|
func SetCtxData(ctx context.Context, ctxData CtxData) context.Context {
|
|
return context.WithValue(ctx, dataKey, ctxData)
|
|
}
|
|
|
|
func GetCtxData(ctx context.Context) CtxData {
|
|
ctxData, _ := ctx.Value(dataKey).(CtxData)
|
|
return ctxData
|
|
}
|
|
|
|
func GetRequestPermissionsFromCtx(ctx context.Context) []string {
|
|
ctxPermission, _ := ctx.Value(requestPermissionsKey).([]string)
|
|
return ctxPermission
|
|
}
|
|
|
|
func checkOrigin(ctx context.Context, origins []string) error {
|
|
origin := grpc.GetGatewayHeader(ctx, http_util.Origin)
|
|
if origin == "" {
|
|
origin = http_util.OriginHeader(ctx)
|
|
if origin == "" {
|
|
return nil
|
|
}
|
|
}
|
|
if http_util.IsOriginAllowed(origins, origin) {
|
|
return nil
|
|
}
|
|
return zerrors.ThrowPermissionDenied(nil, "AUTH-DZG21", "Errors.OriginNotAllowed")
|
|
}
|
|
|
|
func extractBearerToken(token string) (part string, err error) {
|
|
parts := strings.Split(token, BearerPrefix)
|
|
if len(parts) != 2 {
|
|
return "", zerrors.ThrowUnauthenticated(nil, "AUTH-toLo1", "invalid auth header")
|
|
}
|
|
return parts[1], nil
|
|
}
|