2021-07-28 06:31:16 -04:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2020-07-16 22:12:26 -04:00
package auth
import (
"context"
2021-07-28 06:31:16 -04:00
stderrors "errors"
2020-07-16 22:12:26 -04:00
"fmt"
2020-07-22 00:14:28 -04:00
"net/http"
2020-07-16 22:12:26 -04:00
"strings"
2020-08-20 13:34:03 -04:00
"time"
2020-07-16 22:12:26 -04:00
2021-09-07 22:13:19 -04:00
"github.com/hashicorp/boundary/api/recovery"
2020-08-24 13:22:10 -04:00
"github.com/hashicorp/boundary/globals"
2022-10-27 09:01:05 -04:00
"github.com/hashicorp/boundary/internal/auth"
2023-11-02 16:03:47 -04:00
"github.com/hashicorp/boundary/internal/auth/ldap"
"github.com/hashicorp/boundary/internal/auth/oidc"
"github.com/hashicorp/boundary/internal/auth/password"
2022-10-27 09:01:05 -04:00
"github.com/hashicorp/boundary/internal/daemon/controller/common"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
2021-02-11 13:32:45 -06:00
"github.com/hashicorp/boundary/internal/errors"
2023-09-12 08:15:34 -04:00
"github.com/hashicorp/boundary/internal/event"
2021-10-19 17:21:41 -04:00
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
2020-09-14 12:30:11 -04:00
"github.com/hashicorp/boundary/internal/gen/controller/tokens"
2022-08-05 14:03:44 +01:00
"github.com/hashicorp/boundary/internal/iam"
2020-08-20 13:34:03 -04:00
"github.com/hashicorp/boundary/internal/kms"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/perms"
2021-05-04 19:57:35 -05:00
"github.com/hashicorp/boundary/internal/requests"
2022-06-20 18:20:17 -07:00
"github.com/hashicorp/boundary/internal/server"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/types/action"
2020-09-16 11:15:42 -04:00
"github.com/hashicorp/boundary/internal/types/resource"
2020-08-10 17:15:02 -04:00
"github.com/hashicorp/boundary/internal/types/scope"
2022-10-27 09:01:05 -04:00
"github.com/hashicorp/boundary/internal/util"
"github.com/hashicorp/boundary/internal/util/template"
2021-08-26 10:44:41 -04:00
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
2022-03-16 17:04:07 -04:00
wrapping "github.com/hashicorp/go-kms-wrapping/v2"
2020-09-26 14:01:49 -04:00
"github.com/mr-tron/base58"
2020-08-25 00:20:50 -04:00
"google.golang.org/protobuf/proto"
2020-07-16 22:12:26 -04:00
)
2021-10-19 17:21:41 -04:00
type TokenFormat uint32
2020-07-22 00:14:28 -04:00
const (
2020-09-08 23:57:26 -04:00
// We weren't given one or couldn't parse it
2020-07-22 00:14:28 -04:00
AuthTokenTypeUnknown TokenFormat = iota
2020-08-25 00:20:50 -04:00
// Came in via the Authentication: Bearer header
2020-07-22 00:14:28 -04:00
AuthTokenTypeBearer
2020-08-25 00:20:50 -04:00
// Came in via split cookies
2020-07-22 00:14:28 -04:00
AuthTokenTypeSplitCookie
2020-08-25 00:20:50 -04:00
// It's of recovery type
AuthTokenTypeRecoveryKms
2020-07-22 00:14:28 -04:00
)
2020-07-16 22:12:26 -04:00
type key int
2020-08-24 13:22:10 -04:00
var verifierKey key
2020-07-16 22:12:26 -04:00
2020-07-22 00:14:28 -04:00
type VerifyResults struct {
2022-10-27 09:01:05 -04:00
UserData template . Data
// This is copied out from UserData above but used to avoid nil checks in
// lots of places that embed this value
2021-04-13 16:55:40 -05:00
UserId string
AuthTokenId string
Error error
Scope * scopes . ScopeInfo
// AuthenticatedFinished means that the request has passed through the
// authentication system successfully. This does _not_ indicate whether a
// token was provided on the request. Requests for `u_anon` will still have
// this set true! This is because if a request has a token that is invalid,
// we fall back to `u_anon` because the request may still be allowed for any
// anonymous user; it simply fails to validate for and look up grants for an
// actual known user.
//
// A good example is when running dev mode twice. The first time you can
// authenticate and get a token which is saved by the token helper. The
// second time, you run a command and it reads the token from the helper.
// That token is no longer valid, but if the action is granted to `u_anon`
// the action should still succeed. What happens internally is that the
// token fails to look up a non-anonymous user, so we fallback to the
// anonymous user, which is the default.
//
// If you want to know if the request had a valid token provided, use a
// switch on UserId. Anything that isn't `u_anon` will have to have had a
// valid token provided. And a valid token will never fall back to `u_anon`.
AuthenticationFinished bool
2020-09-23 23:39:44 -04:00
2020-10-22 08:40:00 -04:00
// RoundTripValue can be set to allow the function performing authentication
// (often accompanied by lookup(s)) to return a result of that lookup to the
// calling function. It is opaque to this package.
2022-10-25 18:29:38 -05:00
RoundTripValue any
2020-10-22 08:40:00 -04:00
2020-09-23 23:39:44 -04:00
// Used for additional verification
v * verifier
2020-07-16 22:12:26 -04:00
}
type verifier struct {
2022-10-27 09:01:05 -04:00
iamRepoFn common . IamRepoFactory
authTokenRepoFn common . AuthTokenRepoFactory
serversRepoFn common . ServersRepoFactory
passwordAuthRepoFn common . PasswordAuthRepoFactory
oidcAuthRepoFn common . OidcAuthRepoFactory
2023-03-15 11:40:40 -04:00
ldapAuthRepoFn common . LdapAuthRepoFactory
2022-10-27 09:01:05 -04:00
kms * kms . Kms
requestInfo * authpb . RequestInfo
res * perms . Resource
act action . Type
ctx context . Context
acl perms . ACL
}
// TODO (jefferai 10/2022): NewVerifierContextWithAccounts performs the function
// of NewVerifierContext (see the docs for that function) but with extra
// parameters that can be used to look up account information. This is not
// intended to be a long-lived function; see
// https://hashicorp.atlassian.net/browse/ICU-6571 and
// https://hashicorp.atlassian.net/browse/ICU-6572
//
// This is being added for a quick turnaround purpose and to avoid making large
// numbers of changes to tests when we may do a much bigger refactor; when those
// items are addressed this can be removed.
func NewVerifierContextWithAccounts ( ctx context . Context ,
iamRepoFn common . IamRepoFactory ,
authTokenRepoFn common . AuthTokenRepoFactory ,
serversRepoFn common . ServersRepoFactory ,
passwordAuthRepoFn common . PasswordAuthRepoFactory ,
oidcAuthRepoFn common . OidcAuthRepoFactory ,
2023-03-15 11:40:40 -04:00
ldapAuthRepoFn common . LdapAuthRepoFactory ,
2022-10-27 09:01:05 -04:00
kms * kms . Kms ,
requestInfo * authpb . RequestInfo ,
) context . Context {
return context . WithValue ( ctx , verifierKey , & verifier {
iamRepoFn : iamRepoFn ,
authTokenRepoFn : authTokenRepoFn ,
serversRepoFn : serversRepoFn ,
passwordAuthRepoFn : passwordAuthRepoFn ,
oidcAuthRepoFn : oidcAuthRepoFn ,
2023-03-15 11:40:40 -04:00
ldapAuthRepoFn : ldapAuthRepoFn ,
2022-10-27 09:01:05 -04:00
kms : kms ,
requestInfo : requestInfo ,
})
2020-07-16 22:12:26 -04:00
}
// NewVerifierContext creates a context that carries a verifier object from the
// HTTP handlers to the gRPC service handlers. It should only be created in the
// HTTP handler and should exist for every request that reaches the service
// handlers.
func NewVerifierContext ( ctx context . Context ,
iamRepoFn common . IamRepoFactory ,
authTokenRepoFn common . AuthTokenRepoFactory ,
2020-08-24 13:22:10 -04:00
serversRepoFn common . ServersRepoFactory ,
2020-08-20 13:34:03 -04:00
kms * kms . Kms ,
2022-04-19 11:42:54 -04:00
requestInfo * authpb . RequestInfo ,
) context . Context {
2023-03-15 11:40:40 -04:00
return NewVerifierContextWithAccounts ( ctx , iamRepoFn , authTokenRepoFn , serversRepoFn , nil , nil , nil , kms , requestInfo )
2020-07-16 22:12:26 -04:00
}
// Verify takes in a context that has expected parameters as values and runs an
// authn/authz check. It returns a user ID, the scope ID for the request (which
// may come from the URL and may come from the token) and whether or not to
// proceed, e.g. whether the authn/authz check resulted in failure. If an error
// occurs it's logged to the system log.
2020-07-22 15:10:44 -04:00
func Verify ( ctx context . Context , opt ... Option ) ( ret VerifyResults ) {
2021-07-28 06:31:16 -04:00
const op = "auth.Verify"
2020-08-16 00:42:36 -04:00
ret . Error = handlers . ForbiddenError ()
2020-07-16 22:12:26 -04:00
v , ok := ctx . Value ( verifierKey ).( * verifier )
if ! ok {
// We don't have a logger yet and this should never happen in any
// context we won't catch in tests
panic ( "no verifier information found in context" )
}
2020-09-23 23:39:44 -04:00
ret . v = v
2020-09-01 12:31:54 -04:00
v . ctx = ctx
2021-10-28 21:14:39 -04:00
ea := & event . Auth {}
defer event . WriteAudit ( ctx , op , event . WithAuth ( ea ))
2020-08-13 17:39:48 -04:00
opts := getOpts ( opt ... )
2020-09-01 12:31:54 -04:00
2020-07-22 00:14:28 -04:00
ret . Scope = new ( scopes . ScopeInfo )
2021-03-25 03:15:43 -04:00
2021-05-04 19:57:35 -05:00
// Note: we don't call RequestContextFromCtx here because that performs a
// SelfOrDefault. That's useful in the handlers, but here we don't want to
// do anything if it's nil. That's mainly useful in tests where
// DisableAuthEntirely is set. Later, if we don't have the value set at all,
// we have some safeguards to ensure we fail safe (e.g. no output fields at
// all). So it provides a good indication as well whether we have set this
// where and when needed.
var reqInfo * requests . RequestContext
reqInfoRaw := ctx . Value ( requests . ContextRequestInformationKey )
if reqInfoRaw != nil {
reqInfo = reqInfoRaw .( * requests . RequestContext )
}
2021-03-25 03:15:43 -04:00
// In tests we often simply disable auth so we can test the service handlers
// without fuss
2020-07-22 00:14:28 -04:00
if v . requestInfo . DisableAuthEntirely {
2021-10-28 21:14:39 -04:00
yes := true
ea . DisabledAuthEntirely = & yes
2021-03-25 03:15:43 -04:00
const op = "auth.(disabled).lookupScope"
2021-10-19 17:21:41 -04:00
ret . Scope . Id = v . requestInfo . ScopeIdOverride
2020-09-01 12:31:54 -04:00
if ret . Scope . Id == "" {
ret . Scope . Id = opts . withScopeId
}
2021-03-25 03:15:43 -04:00
// Look up scope details to return. We can skip a lookup when using the
// global scope
switch ret . Scope . Id {
2021-11-04 09:34:01 -04:00
case scope . Global . String ():
2021-03-25 03:15:43 -04:00
ret . Scope = & scopes . ScopeInfo {
Id : scope . Global . String (),
Type : scope . Global . String (),
Name : scope . Global . String (),
Description : "Global Scope" ,
ParentScopeId : "" ,
}
default :
iamRepo , err := v . iamRepoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
ret . Error = errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to get iam repo" ))
2021-03-25 03:15:43 -04:00
return
}
scp , err := iamRepo . LookupScope ( v . ctx , ret . Scope . Id )
if err != nil {
2021-07-29 18:52:56 -06:00
ret . Error = errors . Wrap ( ctx , err , op )
2021-03-25 03:15:43 -04:00
return
}
if scp == nil {
2022-04-29 09:52:01 -04:00
ret . Error = errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprintf ( "non-existent scope %q" , ret . Scope . Id ))
2021-03-25 03:15:43 -04:00
return
}
ret . Scope = & scopes . ScopeInfo {
Id : scp . GetPublicId (),
Type : scp . GetType (),
Name : scp . GetName (),
Description : scp . GetDescription (),
ParentScopeId : scp . GetParentId (),
}
2020-07-22 00:14:28 -04:00
}
2021-10-19 17:21:41 -04:00
ret . UserId = v . requestInfo . UserIdOverride
2022-10-27 09:01:05 -04:00
ret . UserData . User . Id = util . Pointer ( ret . UserId )
2021-05-04 19:57:35 -05:00
if reqInfo != nil {
reqInfo . UserId = ret . UserId
}
2021-10-28 21:14:39 -04:00
ea . UserInfo = & event . UserInfo { UserId : ret . UserId }
2020-08-16 00:42:36 -04:00
ret . Error = nil
2020-07-22 00:14:28 -04:00
return
}
2020-08-20 13:34:03 -04:00
2020-09-08 13:27:51 -07:00
v . act = opts . withAction
v . res = & perms . Resource {
ScopeId : opts . withScopeId ,
Id : opts . withId ,
Pin : opts . withPin ,
Type : opts . withType ,
2020-07-16 22:12:26 -04:00
}
2020-09-16 11:15:42 -04:00
// Global scope has no parent ID; account for this
if opts . withId == scope . Global . String () && opts . withType == resource . Scope {
v . res . ScopeId = scope . Global . String ()
}
2020-07-16 22:12:26 -04:00
2020-09-14 12:30:11 -04:00
if v . requestInfo . EncryptedToken != "" {
2021-07-28 06:31:16 -04:00
v . decryptToken ( ctx )
2020-09-14 12:30:11 -04:00
}
2020-09-23 23:39:44 -04:00
var authResults perms . ACLResults
2021-10-28 21:14:39 -04:00
var grantTuples [] perms . GrantTuple
2022-10-27 09:01:05 -04:00
var userData template . Data
2020-07-22 00:14:28 -04:00
var err error
2022-10-27 09:01:05 -04:00
authResults , ret . UserData , ret . Scope , v . acl , grantTuples , err = v . performAuthCheck ( ctx )
2020-07-16 22:12:26 -04:00
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error performing authn/authz check" ))
2020-07-16 22:12:26 -04:00
return
}
2020-09-23 23:39:44 -04:00
2022-10-27 09:01:05 -04:00
if ret . UserData . User . Id != nil {
ret . UserId = * ret . UserData . User . Id
}
2020-09-19 00:18:17 -04:00
ret . AuthTokenId = v . requestInfo . PublicId
2021-04-13 16:55:40 -05:00
ret . AuthenticationFinished = authResults . AuthenticationFinished
2021-03-24 15:33:04 -04:00
if ! authResults . Authorized {
2020-07-16 22:12:26 -04:00
if v . requestInfo . DisableAuthzFailures {
2020-08-16 00:42:36 -04:00
ret . Error = nil
// TODO: Decide whether to remove this
2021-07-28 06:31:16 -04:00
err := event . WriteObservation ( ctx , op ,
event . WithHeader (
"auth-results" , struct {
Msg string `json:"msg"`
Resource * perms . Resource `json:"resource"`
UserId string `json:"user_id"`
Action string `json:"action"`
}{
Msg : "failed authz info for request" ,
Resource : v . res ,
UserId : ret . UserId ,
Action : v . act . String (),
}))
if err != nil {
event . WriteError ( ctx , op , err )
}
2020-07-16 22:12:26 -04:00
} else {
2020-09-08 23:57:26 -04:00
// If the anon user was used (either no token, or invalid (perhaps
// expired) token), return a 401. That way if it's an authn'd user
// that is not authz'd we'll return 403 to be explicit.
2022-10-28 15:52:06 -04:00
if ret . UserId == globals . AnonymousUserId {
2020-09-08 23:57:26 -04:00
ret . Error = handlers . UnauthenticatedError ()
}
2021-10-28 21:14:39 -04:00
ea . UserInfo = & event . UserInfo {
UserId : ret . UserId ,
}
2020-07-16 22:12:26 -04:00
return
}
}
2021-10-28 21:14:39 -04:00
grants := make ([] event . Grant , 0 , len ( grantTuples ))
for _ , g := range grantTuples {
grants = append ( grants , event . Grant {
Grant : g . Grant ,
RoleId : g . RoleId ,
ScopeId : g . ScopeId ,
})
}
ea . UserInfo = & event . UserInfo {
2022-10-27 09:01:05 -04:00
UserId : ret . UserId ,
}
if userData . Account . Id != nil {
ea . UserInfo . AuthAccountId = * userData . Account . Id
2021-10-28 21:14:39 -04:00
}
ea . GrantsInfo = & event . GrantsInfo {
Grants : grants ,
}
2022-10-27 09:01:05 -04:00
if userData . User . FullName != nil {
ea . UserName = * userData . User . FullName
}
if userData . User . Email != nil {
ea . UserEmail = * userData . User . Email
}
2021-10-28 21:14:39 -04:00
2021-05-04 19:57:35 -05:00
if reqInfo != nil {
reqInfo . UserId = ret . UserId
reqInfo . OutputFields = authResults . OutputFields
}
2020-08-16 00:42:36 -04:00
ret . Error = nil
2020-07-16 22:12:26 -04:00
return
}
2021-07-28 06:31:16 -04:00
func ( v * verifier ) decryptToken ( ctx context . Context ) {
const op = "auth.(verifier).decryptToken"
2020-12-17 10:30:40 -05:00
switch v . requestInfo . TokenFormat {
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeUnknown ):
2020-12-17 10:30:40 -05:00
// Nothing to decrypt
return
2020-09-23 23:39:44 -04:00
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeBearer ), uint32 ( AuthTokenTypeSplitCookie ):
2020-12-17 10:30:40 -05:00
if v . kms == nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "no KMS object available to authz system" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
tokenRepo , err := v . authTokenRepoFn ()
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "failed to get authtoken repo" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
at , err := tokenRepo . LookupAuthToken ( v . ctx , v . requestInfo . PublicId )
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "failed to look up auth token by public ID" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
if at == nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "nil result from looking up auth token by public ID" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
tokenWrapper , err := v . kms . GetWrapper ( v . ctx , at . GetScopeId (), kms . KeyPurposeTokens )
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "unable to get wrapper for tokens; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
version := v . requestInfo . EncryptedToken [ 0 : len ( globals . ServiceTokenV1 )]
switch version {
case globals . ServiceTokenV1 :
default :
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "unknown token encryption version; continuing as anonymous user" ), event . WithInfo ( "version" , version ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
marshaledToken , err := base58 . FastBase58Decoding ( v . requestInfo . EncryptedToken [ len ( globals . ServiceTokenV1 ):])
2020-09-23 23:39:44 -04:00
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error unmarshaling base58 token; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-09-23 23:39:44 -04:00
return
}
2022-03-16 17:04:07 -04:00
blobInfo := new ( wrapping . BlobInfo )
2020-12-17 10:30:40 -05:00
if err := proto . Unmarshal ( marshaledToken , blobInfo ); err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error decoding encrypted token; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2022-03-16 17:04:07 -04:00
s1Bytes , err := tokenWrapper . Decrypt ( v . ctx , blobInfo , wrapping . WithAad ([] byte ( v . requestInfo . PublicId )))
2020-12-17 10:30:40 -05:00
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error decrypting encrypted token; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
2020-09-23 23:39:44 -04:00
}
2020-12-17 10:30:40 -05:00
var s1Info tokens . S1TokenInfo
if err := proto . Unmarshal ( s1Bytes , & s1Info ); err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error unmarshaling token info; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2021-10-19 17:21:41 -04:00
if v . requestInfo . TokenFormat == uint32 ( AuthTokenTypeUnknown ) || s1Info . Token == "" || v . requestInfo . PublicId == "" {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "after parsing, could not find valid token; continuing as anonymous user" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2020-09-23 23:39:44 -04:00
2020-12-17 10:30:40 -05:00
v . requestInfo . Token = s1Info . Token
return
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeRecoveryKms ):
2020-12-17 10:30:40 -05:00
if v . kms == nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "no KMS object available to authz system" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2022-05-13 13:53:22 -04:00
wrapper := v . kms . GetExternalWrappers ( ctx ). Recovery ()
2020-12-17 10:30:40 -05:00
if wrapper == nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "no recovery KMS is available" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-09-23 23:39:44 -04:00
return
}
2020-12-17 10:30:40 -05:00
info , err := recovery . ParseRecoveryToken ( v . ctx , wrapper , v . requestInfo . EncryptedToken )
if err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "decrypt recovery token: error parsing and validating recovery token" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
// If we add the validity period to the creation time (which we've
// verified is before the current time, with a minute of fudging), and
// it's before now, it's expired and might be a replay.
if info . CreationTime . Add ( globals . RecoveryTokenValidityPeriod ). Before ( time . Now ()) {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "recovery token has expired (possible replay attack)" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
repo , err := v . serversRepoFn ()
if err != nil {
2022-06-20 18:20:17 -07:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "decrypt recovery token: error fetching server repo" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2022-06-20 18:20:17 -07:00
if err := repo . AddNonce ( v . ctx , info . Nonce , server . NoncePurposeRecovery ); err != nil {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "decrypt recovery token: error adding nonce to database (possible replay attack)" ))
2021-10-19 17:21:41 -04:00
v . requestInfo . TokenFormat = uint32 ( AuthTokenTypeUnknown )
2020-12-17 10:30:40 -05:00
return
}
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "recovery KMS was used to authorize a call" ), event . WithInfo ( "url" , v . requestInfo . Path , "method" , v . requestInfo . Method ))
2020-09-23 23:39:44 -04:00
}
}
2021-10-28 21:14:39 -04:00
func ( v verifier ) performAuthCheck ( ctx context . Context ) (
aclResults perms . ACLResults ,
2022-10-27 09:01:05 -04:00
userData template . Data ,
2021-10-28 21:14:39 -04:00
scopeInfo * scopes . ScopeInfo ,
retAcl perms . ACL ,
grantTuples [] perms . GrantTuple ,
2022-04-19 11:42:54 -04:00
retErr error ,
) {
2021-02-11 13:32:45 -06:00
const op = "auth.(verifier).performAuthCheck"
2020-07-16 22:12:26 -04:00
// Ensure we return an error by default if we forget to set this somewhere
2021-10-28 21:14:39 -04:00
retErr = errors . New ( ctx , errors . Unknown , op , "default auth error" , errors . WithoutEvent ())
2020-07-22 00:14:28 -04:00
// Make the linter happy
_ = retErr
scopeInfo = new ( scopes . ScopeInfo )
2022-10-27 09:01:05 -04:00
// This will always be set, so further down below we can switch on whether
// it's empty
2022-10-28 15:52:06 -04:00
userData . User . Id = util . Pointer ( globals . AnonymousUserId )
2020-07-16 22:12:26 -04:00
// Validate the token and fetch the corresponding user ID
2020-08-20 13:34:03 -04:00
switch v . requestInfo . TokenFormat {
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeUnknown ):
2020-09-14 12:30:11 -04:00
// Nothing; remain as the anonymous user
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeRecoveryKms ):
2020-09-14 12:30:11 -04:00
// We validated the encrypted token in decryptToken and handled the
// nonces there, so just set the user
2022-10-28 15:52:06 -04:00
userData . User . Id = util . Pointer ( globals . RecoveryUserId )
2020-09-14 12:30:11 -04:00
2021-10-19 17:21:41 -04:00
case uint32 ( AuthTokenTypeBearer ), uint32 ( AuthTokenTypeSplitCookie ):
2020-08-26 23:28:38 -04:00
if v . requestInfo . Token == "" {
// This will end up staying as the anonymous user
break
}
2020-08-07 10:18:40 -07:00
tokenRepo , err := v . authTokenRepoFn ()
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op )
2020-08-07 10:18:40 -07:00
return
}
at , err := tokenRepo . ValidateToken ( v . ctx , v . requestInfo . PublicId , v . requestInfo . Token )
if err != nil {
2020-09-08 23:57:26 -04:00
// Continue as the anonymous user as maybe this token is expired but
// we can still perform the action
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , err , event . WithInfoMsg ( "error validating token; continuing as anonymous user" ))
2020-09-08 23:57:26 -04:00
break
2020-08-07 10:18:40 -07:00
}
if at != nil {
2022-10-27 09:01:05 -04:00
userData . Account . Id = util . Pointer ( at . GetAuthAccountId ())
userData . User . Id = util . Pointer ( at . GetIamUserId ())
if * userData . User . Id == "" {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "perform auth check: valid token did not map to a user, likely because no account is associated with the user any longer; continuing as u_anon" ), event . WithInfo ( "token_id" , at . GetPublicId ()))
2022-10-28 15:52:06 -04:00
userData . User . Id = util . Pointer ( globals . AnonymousUserId )
2022-10-27 09:01:05 -04:00
userData . Account . Id = nil
2020-09-26 13:58:48 -04:00
}
2020-08-07 10:18:40 -07:00
}
2020-07-22 00:14:28 -04:00
}
2020-07-16 22:12:26 -04:00
iamRepo , err := v . iamRepoFn ()
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to get iam repo" ))
return
}
2022-10-27 09:01:05 -04:00
u , _ , err := iamRepo . LookupUser ( ctx , * userData . User . Id )
2021-10-28 21:14:39 -04:00
if err != nil {
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "failed to lookup user" ))
2020-07-16 22:12:26 -04:00
return
}
2022-10-27 09:01:05 -04:00
userData . User . Name = util . Pointer ( u . Name )
userData . User . Email = util . Pointer ( u . Email )
userData . User . FullName = util . Pointer ( u . FullName )
2023-03-15 11:40:40 -04:00
if userData . Account . Id != nil && * userData . Account . Id != "" && v . passwordAuthRepoFn != nil && v . oidcAuthRepoFn != nil && v . ldapAuthRepoFn != nil {
2022-10-27 09:01:05 -04:00
const domain = "auth"
var acct auth . Account
var err error
2023-11-15 19:55:46 -05:00
switch globals . ResourceInfoFromPrefix ( * userData . Account . Id ). Subtype {
2023-11-02 16:03:47 -04:00
case password . Subtype :
2022-10-27 09:01:05 -04:00
repo , repoErr := v . passwordAuthRepoFn ()
if repoErr != nil {
retErr = errors . Wrap ( ctx , repoErr , op , errors . WithMsg ( "failed to get password auth repo" ))
return
}
acct , err = repo . LookupAccount ( ctx , * userData . Account . Id )
2023-11-02 16:03:47 -04:00
case oidc . Subtype :
2022-10-27 09:01:05 -04:00
repo , repoErr := v . oidcAuthRepoFn ()
if repoErr != nil {
retErr = errors . Wrap ( ctx , repoErr , op , errors . WithMsg ( "failed to get oidc auth repo" ))
return
}
acct , err = repo . LookupAccount ( ctx , * userData . Account . Id )
2023-11-02 16:03:47 -04:00
case ldap . Subtype :
2023-03-15 11:40:40 -04:00
repo , repoErr := v . ldapAuthRepoFn ()
if repoErr != nil {
retErr = errors . Wrap ( ctx , repoErr , op , errors . WithMsg ( "failed to get ldap auth repo" ))
return
}
acct , err = repo . LookupAccount ( ctx , * userData . Account . Id )
2022-10-27 09:01:05 -04:00
default :
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "unrecognized account id type" ))
return
}
if err != nil {
if errors . IsNotFoundError ( err ) {
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "account doesn't exist" ))
return
}
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( "error looking up account" ))
return
}
userData . Account . Name = util . Pointer ( acct . GetName ())
userData . Account . Email = util . Pointer ( acct . GetEmail ())
userData . Account . LoginName = util . Pointer ( acct . GetLoginName ())
userData . Account . Subject = util . Pointer ( acct . GetSubject ())
}
2020-07-22 00:14:28 -04:00
2020-08-26 23:28:38 -04:00
// Look up scope details to return. We can skip a lookup when using the
// global scope
switch v . res . ScopeId {
2021-11-04 09:34:01 -04:00
case scope . Global . String ():
2020-08-26 23:28:38 -04:00
scopeInfo = & scopes . ScopeInfo {
Id : scope . Global . String (),
Type : scope . Global . String (),
Name : scope . Global . String (),
Description : "Global Scope" ,
ParentScopeId : "" ,
}
default :
scp , err := iamRepo . LookupScope ( v . ctx , v . res . ScopeId )
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op )
2020-08-26 23:28:38 -04:00
return
}
if scp == nil {
2021-10-28 21:14:39 -04:00
retErr = errors . New ( ctx , errors . InvalidParameter , op , fmt . Sprint ( "non-existent scope $q" , v . res . ScopeId ))
2020-08-26 23:28:38 -04:00
return
}
scopeInfo = & scopes . ScopeInfo {
Id : scp . GetPublicId (),
Type : scp . GetType (),
Name : scp . GetName (),
Description : scp . GetDescription (),
ParentScopeId : scp . GetParentId (),
}
2020-07-22 00:14:28 -04:00
}
2020-08-20 13:34:03 -04:00
// At this point we don't need to look up grants since it's automatically allowed
2021-10-19 17:21:41 -04:00
if v . requestInfo . TokenFormat == uint32 ( AuthTokenTypeRecoveryKms ) {
2021-04-13 16:55:40 -05:00
aclResults . AuthenticationFinished = true
2021-03-24 15:33:04 -04:00
aclResults . Authorized = true
2020-08-20 13:34:03 -04:00
retErr = nil
return
}
2020-07-22 00:14:28 -04:00
var parsedGrants [] perms . Grant
2020-08-26 23:28:38 -04:00
// Fetch and parse grants for this user ID (which may include grants for
// u_anon and u_auth)
2022-10-27 09:01:05 -04:00
grantTuples , err = iamRepo . GrantsForUser ( v . ctx , * userData . User . Id )
2020-07-16 22:12:26 -04:00
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op )
2020-07-16 22:12:26 -04:00
return
}
2021-06-07 09:19:21 -05:00
parsedGrants = make ([] perms . Grant , 0 , len ( grantTuples ))
2021-02-05 12:39:55 -05:00
// Note: Below, we always skip validation so that we don't error on formats
// that we've since restricted, e.g. "id=foo;actions=create,read". These
// will simply not have an effect.
2021-06-07 09:19:21 -05:00
for _ , pair := range grantTuples {
2022-10-27 09:01:05 -04:00
permsOpts := [] perms . Option {
perms . WithUserId ( * userData . User . Id ),
perms . WithSkipFinalValidation ( true ),
}
if userData . Account . Id != nil {
permsOpts = append ( permsOpts , perms . WithAccountId ( * userData . Account . Id ))
}
2020-10-01 10:51:54 -04:00
parsed , err := perms . Parse (
2023-06-06 16:13:44 -04:00
ctx ,
2020-10-01 10:51:54 -04:00
pair . ScopeId ,
pair . Grant ,
2022-10-27 09:01:05 -04:00
permsOpts ... )
2020-07-16 22:12:26 -04:00
if err != nil {
2021-10-28 21:14:39 -04:00
retErr = errors . Wrap ( ctx , err , op , errors . WithMsg ( fmt . Sprintf ( "failed to parse grant %#v" , pair . Grant )))
2020-07-16 22:12:26 -04:00
return
}
parsedGrants = append ( parsedGrants , parsed )
}
2020-09-23 23:39:44 -04:00
retAcl = perms . NewACL ( parsedGrants ... )
2022-10-27 09:01:05 -04:00
aclResults = retAcl . Allowed ( * v . res , v . act , * userData . User . Id )
2021-03-24 15:33:04 -04:00
// We don't set authenticated above because setting this but not authorized
// is used for further permissions checks, such as during recursive listing.
// So we want to make sure any code relying on that has the full set of
// grants successfully loaded.
2021-04-13 16:55:40 -05:00
aclResults . AuthenticationFinished = true
2020-07-16 22:12:26 -04:00
retErr = nil
return
}
2020-07-22 00:14:28 -04:00
2021-01-20 10:53:01 -05:00
// FetchActionSetForId returns the allowed actions for a given ID using the
// current set of ACLs and all other parameters the same (user, etc.)
func ( r * VerifyResults ) FetchActionSetForId ( ctx context . Context , id string , availableActions action . ActionSet , opt ... Option ) action . ActionSet {
2021-05-04 19:57:35 -05:00
return r . fetchActions ( id , resource . Unknown , availableActions , opt ... )
2021-01-20 10:53:01 -05:00
}
// FetchActionSetForType returns the allowed actions for a given collection type
// using the current set of ACLs and all other parameters the same (user, etc.)
func ( r * VerifyResults ) FetchActionSetForType ( ctx context . Context , typ resource . Type , availableActions action . ActionSet , opt ... Option ) action . ActionSet {
2021-05-04 19:57:35 -05:00
return r . fetchActions ( "" , typ , availableActions , opt ... )
2021-01-20 10:53:01 -05:00
}
2021-05-04 19:57:35 -05:00
func ( r * VerifyResults ) fetchActions ( id string , typ resource . Type , availableActions action . ActionSet , opt ... Option ) action . ActionSet {
2021-01-13 14:42:37 -05:00
switch {
case r . v . requestInfo . DisableAuthEntirely ,
2021-10-19 17:21:41 -04:00
r . v . requestInfo . TokenFormat == uint32 ( AuthTokenTypeRecoveryKms ):
2021-01-13 14:42:37 -05:00
// TODO: See if we can be better about what we return with the anonymous
// user and recovery KMS, perhaps given exclusionary options for each
return availableActions
}
2022-10-27 09:01:05 -04:00
// If there is no user ID set by definition there are no actions to fetch.
// This shouldn't happen because we should always fall back to at least the
// anonymous user so it's defense in depth.
if r . UserData . User . Id == nil {
return nil
}
2021-01-13 14:42:37 -05:00
opts := getOpts ( opt ... )
res := opts . withResource
2021-03-24 15:33:04 -04:00
// If not passed in, use what's already been populated through verification
2021-01-13 14:42:37 -05:00
if res == nil {
res = r . v . res
}
2021-03-24 15:33:04 -04:00
// If this is being called directly we may not have a resource yet
2021-01-13 14:42:37 -05:00
if res == nil {
res = new ( perms . Resource )
}
2021-01-20 10:53:01 -05:00
if id != "" {
res . Id = id
}
if typ != resource . Unknown {
res . Type = typ
}
2021-01-13 14:42:37 -05:00
2021-01-20 10:53:01 -05:00
ret := make ( action . ActionSet , 0 , len ( availableActions ))
2021-01-13 14:42:37 -05:00
for _ , act := range availableActions {
2022-10-27 09:01:05 -04:00
if r . v . acl . Allowed ( * res , act , * r . UserData . User . Id ). Authorized {
2021-01-13 14:42:37 -05:00
ret = append ( ret , act )
}
}
if len ( ret ) == 0 {
return nil
}
return ret
}
2023-01-06 17:27:19 -06:00
func ( r * VerifyResults ) FetchOutputFields ( res perms . Resource , act action . Type ) * perms . OutputFields {
var ret * perms . OutputFields
2021-05-04 19:57:35 -05:00
switch {
2021-10-19 17:21:41 -04:00
case r . v . requestInfo . TokenFormat == uint32 ( AuthTokenTypeRecoveryKms ):
2023-01-06 17:27:19 -06:00
return ret . AddFields ([] string { "*" })
2021-05-04 19:57:35 -05:00
case r . v . requestInfo . DisableAuthEntirely :
2023-01-06 17:27:19 -06:00
return ret
2022-10-27 09:01:05 -04:00
case r . UserData . User . Id == nil :
// If there is no user ID set by definition there are no actions to fetch.
// This shouldn't happen because we should always fall back to at least the
// anonymous user so it's defense in depth.
2023-01-06 17:27:19 -06:00
return ret
2021-05-04 19:57:35 -05:00
}
2022-10-27 09:01:05 -04:00
return r . v . acl . Allowed ( res , act , * r . UserData . User . Id ). OutputFields
2021-05-04 19:57:35 -05:00
}
2022-08-09 12:42:01 +00:00
// ACL returns the perms.ACL of the verifier.
func ( r * VerifyResults ) ACL () perms . ACL {
if r . v == nil {
return perms . ACL {}
}
return r . v . acl
}
2020-07-22 15:10:44 -04:00
// GetTokenFromRequest pulls the token from either the Authorization header or
2020-07-22 00:14:28 -04:00
// split cookies and parses it. If it cannot be parsed successfully, the issue
// is logged and we return blank, so logic will continue as the anonymous user.
2020-09-14 12:30:11 -04:00
// The public ID and _encrypted_ token are returned along with the token format.
2021-10-19 17:21:41 -04:00
func GetTokenFromRequest ( ctx context . Context , kmsCache * kms . Kms , req * http . Request ) ( string , string , uint32 ) {
2021-07-28 06:31:16 -04:00
const op = "auth.GetTokenFromRequest"
2020-07-22 00:14:28 -04:00
// First, get the token, either from the authorization header or from split
// cookies
var receivedTokenType TokenFormat
var fullToken string
if authHeader := req . Header . Get ( "Authorization" ); authHeader != "" {
headerSplit := strings . SplitN ( strings . TrimSpace ( authHeader ), " " , 2 )
if len ( headerSplit ) == 2 && strings . EqualFold ( strings . TrimSpace ( headerSplit [ 0 ]), "bearer" ) {
receivedTokenType = AuthTokenTypeBearer
fullToken = strings . TrimSpace ( headerSplit [ 1 ])
}
}
if receivedTokenType != AuthTokenTypeBearer {
var httpCookiePayload string
var jsCookiePayload string
2020-09-01 09:23:41 -07:00
if hc , err := req . Cookie ( handlers . HttpOnlyCookieName ); err == nil {
2020-07-22 00:14:28 -04:00
httpCookiePayload = hc . Value
}
2020-09-01 09:23:41 -07:00
if jc , err := req . Cookie ( handlers . JsVisibleCookieName ); err == nil {
2020-07-22 00:14:28 -04:00
jsCookiePayload = jc . Value
}
if httpCookiePayload != "" && jsCookiePayload != "" {
receivedTokenType = AuthTokenTypeSplitCookie
fullToken = jsCookiePayload + httpCookiePayload
}
}
if receivedTokenType == AuthTokenTypeUnknown || fullToken == "" {
// We didn't find auth info or a client screwed up and put in a blank
// header instead of nothing at all, so return blank which will indicate
2022-06-09 11:30:01 -04:00
// the anonymous user
2021-10-19 17:21:41 -04:00
return "" , "" , uint32 ( AuthTokenTypeUnknown )
2020-07-22 00:14:28 -04:00
}
2020-08-20 13:34:03 -04:00
if strings . HasPrefix ( fullToken , "r_" ) {
2021-10-19 17:21:41 -04:00
return "" , fullToken , uint32 ( AuthTokenTypeRecoveryKms )
2020-08-20 13:34:03 -04:00
}
2020-07-22 00:14:28 -04:00
splitFullToken := strings . Split ( fullToken , "_" )
if len ( splitFullToken ) != 3 {
2021-07-28 06:31:16 -04:00
event . WriteError ( ctx , op , stderrors . New ( "unexpected number of segments in token; continuing as anonymous user" ), event . WithInfo ( "expected" , 3 , "found" , len ( splitFullToken )))
2021-10-19 17:21:41 -04:00
return "" , "" , uint32 ( AuthTokenTypeUnknown )
2020-07-22 00:14:28 -04:00
}
publicId := strings . Join ( splitFullToken [ 0 : 2 ], "_" )
2020-09-14 12:30:11 -04:00
encryptedToken := splitFullToken [ 2 ]
2020-07-22 00:14:28 -04:00
2021-10-19 17:21:41 -04:00
return publicId , encryptedToken , uint32 ( receivedTokenType )
2020-09-14 12:30:11 -04:00
}
2022-08-05 14:03:44 +01:00
// ScopesAuthorizedForList retrieves and returns all scopes where a user is authorized
// to perform a *list* action on. It looks recursively from `rootScopeId`.
func ( r * VerifyResults ) ScopesAuthorizedForList ( ctx context . Context , rootScopeId string , resourceType resource . Type ) ( map [ string ] * scopes . ScopeInfo , error ) {
const op = "auth.(VerifyResults).ScopesAuthorizedForList"
// Validation
switch {
case resourceType == resource . Unknown :
return nil , errors . New ( ctx , errors . InvalidParameter , op , "unknown resource" )
case r . v . iamRepoFn == nil :
return nil , errors . New ( ctx , errors . InvalidParameter , op , "nil iam repo" )
case rootScopeId == "" :
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing root scope id" )
case r . Scope == nil :
return nil , errors . New ( ctx , errors . InvalidParameter , op , "nil scope in auth results" )
}
repo , err := r . v . iamRepoFn ()
if err != nil {
return nil , err
}
// Get all scopes recursively. Start at global because we need to take into
// account permissions in parent scopes even if they want to scale back the
// returned information to a child scope and its children.
scps , err := repo . ListScopesRecursively ( ctx , scope . Global . String ())
if err != nil {
return nil , err
}
var deferredScopes [] * iam . Scope
var globalHasList bool // Store whether global has list permission
scopeResourceMap := make ( map [ string ] * scopes . ScopeInfo ) // Scope data per scope id
// For each scope, see if we have permission to list that resource in that scope
for _ , scp := range scps {
scpId := scp . GetPublicId ()
aSet := r . FetchActionSetForType ( ctx ,
resource . Unknown , // This is overridden by `WithResource` option.
action . ActionSet { action . List },
WithResource ( & perms . Resource { Type : resourceType , ScopeId : scpId }),
)
// We only expect the action set to be nothing, or list. In case
// this is not the case, we bail out.
2022-10-28 11:17:33 -04:00
switch {
case len ( aSet ) == 0 :
2022-08-05 14:03:44 +01:00
// Defer until we've read all scopes. We do this because if the
// ordering coming back isn't in parent-first ordering our map
// lookup might fail.
deferredScopes = append ( deferredScopes , scp )
2022-10-28 15:52:06 -04:00
case len ( aSet ) == 1 || r . UserId == globals . RecoveryUserId :
2022-08-05 14:03:44 +01:00
if aSet [ 0 ] != action . List {
return nil , errors . New ( ctx , errors . Internal , op , "unexpected action in set" )
}
if scopeResourceMap [ scpId ] == nil {
scopeResourceMap [ scpId ] = & scopes . ScopeInfo {
Id : scp . GetPublicId (),
Type : scp . GetType (),
Name : scp . GetName (),
Description : scp . GetDescription (),
ParentScopeId : scp . GetParentId (),
}
}
if scpId == scope . Global . String () {
globalHasList = true
}
default :
return nil , errors . New ( ctx , errors . Internal , op , "unexpected number of actions back in set" )
}
}
// Now go through the deferred scopes and see if a parent matches
for _ , scp := range deferredScopes {
// If they had list on global scope anything else is automatically
// included; otherwise if they had list on the parent scope, this
// scope is included in the map and is sufficient here.
if globalHasList || scopeResourceMap [ scp . GetParentId ()] != nil {
scpId := scp . GetPublicId ()
if scopeResourceMap [ scpId ] == nil {
scopeResourceMap [ scpId ] = & scopes . ScopeInfo {
Id : scp . GetPublicId (),
Type : scp . GetType (),
Name : scp . GetName (),
Description : scp . GetDescription (),
ParentScopeId : scp . GetParentId (),
}
}
}
}
// Elide out any that aren't under the root scope id
elideScopes := make ([] string , 0 , len ( scopeResourceMap ))
for scpId , scp := range scopeResourceMap {
switch rootScopeId {
// If the root is global, it matches
case scope . Global . String ():
// If the current scope matches the root, it matches
case scpId :
// Or if the parent of this scope is the root (for orgs that would mean
// a root scope ID which is covered in the case above, so this is really
// projects matching an org used as the root)
case scp . ParentScopeId :
default :
elideScopes = append ( elideScopes , scpId )
}
}
for _ , scpId := range elideScopes {
delete ( scopeResourceMap , scpId )
}
// If we have nothing in scopeInfoMap at this point, we aren't authorized
// anywhere so return 403.
if len ( scopeResourceMap ) == 0 {
return nil , handlers . ForbiddenError ()
}
return scopeResourceMap , nil
}