You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/auth/oidc/service_token_request.go

99 lines
3.6 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package oidc
import (
"context"
"fmt"
"time"
"github.com/hashicorp/boundary/internal/auth/oidc/request"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/kms"
"google.golang.org/protobuf/proto"
)
// TokenRequest is an oidc domain service function for processing a token
// request from a Boundary client. Token requests are the result of a Boundary
// client polling the tokenUrl they received via StartAuth. On success, it
// returns Boundary token.
//
// * Decrypt the tokenRequestId. If encryption fails, it returns an error.
//
// * Use the authtoken.(Repository).IssueAuthToken to issue the request id's
// token and mark it as issued in the repo. If the token is already issue, an
// error is returned.
func TokenRequest(ctx context.Context, kms *kms.Kms, atRepoFn AuthTokenRepoFactory, authMethodId, tokenRequestId string) (*authtoken.AuthToken, error) {
const op = "oidc.TokenRequest"
if kms == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing kms")
}
if atRepoFn == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth token repo function")
}
if authMethodId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing auth method id")
}
if tokenRequestId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing token request id")
}
reqTkWrapper, err := UnwrapMessage(ctx, tokenRequestId)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
if reqTkWrapper.ScopeId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "request token id wrapper missing scope id")
}
if reqTkWrapper.AuthMethodId == "" {
return nil, errors.New(ctx, errors.InvalidParameter, op, "request token id wrapper missing auth method id")
}
if reqTkWrapper.AuthMethodId != authMethodId {
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s auth method id does not match request wrapper auth method id: %s", authMethodId, reqTkWrapper.AuthMethodId))
}
// tokenRequestId is a proto request.Wrapper, which contains a cipher text field,
// so we need the derived wrapper that was used to encrypt it.
requestWrapper, err := requestWrappingWrapper(ctx, kms, reqTkWrapper.ScopeId, reqTkWrapper.AuthMethodId)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
reqTkBytes, err := decryptMessage(ctx, requestWrapper, reqTkWrapper)
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
var reqTk request.Token
if err := proto.Unmarshal(reqTkBytes, &reqTk); err != nil {
return nil, errors.New(ctx, errors.Unknown, op, "unable to unmarshal request token", errors.WithWrap(err))
}
if reqTk.ExpirationTime == nil {
return nil, errors.New(ctx, errors.InvalidParameter, op, "missing request token id expiration time")
}
// before proceeding, make sure the request hasn't timed out
if time.Now().After(reqTk.ExpirationTime.Timestamp.AsTime()) {
return nil, errors.New(ctx, errors.AuthAttemptExpired, op, "request token id has expired")
}
tokenRepo, err := atRepoFn()
if err != nil {
return nil, errors.Wrap(ctx, err, op)
}
authTk, err := tokenRepo.IssueAuthToken(ctx, reqTk.RequestId)
if err != nil {
if errors.Match(errors.T(errors.RecordNotFound), err) {
// We don't have it -- at least not yet. So don't mark it as an
// error, but nothing is returned.
return nil, nil
}
return nil, errors.Wrap(ctx, err, op)
}
if authTk.Token == "" {
return nil, errors.New(ctx, errors.Internal, op, "issued token is missing")
}
return authTk, nil
}