2021-04-12 17:17:28 -04:00
// Copyright (c) HashiCorp, Inc.
2021-04-08 20:54:32 -04:00
// SPDX-License-Identifier: BUSL-1.1
2023-02-02 17:15:17 -08:00
2021-04-08 20:54:32 -04:00
package oidc
import (
"context"
2021-04-12 17:17:28 -04:00
"fmt"
2021-04-08 20:54:32 -04:00
"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.
2021-04-12 17:17:28 -04:00
func TokenRequest ( ctx context . Context , kms * kms . Kms , atRepoFn AuthTokenRepoFactory , authMethodId , tokenRequestId string ) ( * authtoken . AuthToken , error ) {
2021-04-08 20:54:32 -04:00
const op = "oidc.TokenRequest"
if kms == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing kms" )
2021-04-08 20:54:32 -04:00
}
if atRepoFn == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing auth token repo function" )
2021-04-08 20:54:32 -04:00
}
2021-04-12 17:17:28 -04:00
if authMethodId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing auth method id" )
2021-04-12 17:17:28 -04:00
}
if tokenRequestId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing token request id" )
2021-04-12 17:17:28 -04:00
}
2021-04-08 20:54:32 -04:00
2021-05-02 15:25:29 -04:00
reqTkWrapper , err := UnwrapMessage ( ctx , tokenRequestId )
2021-04-08 20:54:32 -04:00
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
if reqTkWrapper . ScopeId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "request token id wrapper missing scope id" )
2021-04-08 20:54:32 -04:00
}
if reqTkWrapper . AuthMethodId == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "request token id wrapper missing auth method id" )
2021-04-08 20:54:32 -04:00
}
2021-04-12 17:17:28 -04:00
if reqTkWrapper . AuthMethodId != authMethodId {
2021-07-29 18:52:56 -06:00
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 ))
2021-04-12 17:17:28 -04:00
}
2021-04-08 20:54:32 -04:00
// 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 {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
reqTkBytes , err := decryptMessage ( ctx , requestWrapper , reqTkWrapper )
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
var reqTk request . Token
if err := proto . Unmarshal ( reqTkBytes , & reqTk ); err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . Unknown , op , "unable to unmarshal request token" , errors . WithWrap ( err ))
2021-04-08 20:54:32 -04:00
}
if reqTk . ExpirationTime == nil {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . InvalidParameter , op , "missing request token id expiration time" )
2021-04-08 20:54:32 -04:00
}
// before proceeding, make sure the request hasn't timed out
if time . Now (). After ( reqTk . ExpirationTime . Timestamp . AsTime ()) {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . AuthAttemptExpired , op , "request token id has expired" )
2021-04-08 20:54:32 -04:00
}
tokenRepo , err := atRepoFn ()
if err != nil {
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
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
}
2021-07-29 18:52:56 -06:00
return nil , errors . Wrap ( ctx , err , op )
2021-04-08 20:54:32 -04:00
}
if authTk . Token == "" {
2021-07-29 18:52:56 -06:00
return nil , errors . New ( ctx , errors . Internal , op , "issued token is missing" )
2021-04-08 20:54:32 -04:00
}
return authTk , nil
}