api: add OIDC HTTP API endpoints and SDK.

This commit is contained in:
James Rasell 2023-01-13 13:15:58 +00:00
parent 485d216ab3
commit b3a6cfecc4
No known key found for this signature in database
GPG Key ID: AA7D460F5C8377AA
5 changed files with 536 additions and 6 deletions

View File

@ -442,6 +442,38 @@ func (a *ACLBindingRules) Get(bindingRuleID string, q *QueryOptions) (*ACLBindin
return &resp, qm, nil
}
// ACLOIDC is used to query the ACL OIDC endpoints.
type ACLOIDC struct {
client *Client
}
// ACLOIDC returns a new handle on the ACL auth-methods API client.
func (c *Client) ACLOIDC() *ACLOIDC {
return &ACLOIDC{client: c}
}
// GetAuthURL generates the OIDC provider authentication URL. This URL should
// be visited in order to sign in to the provider.
func (a *ACLOIDC) GetAuthURL(req *ACLOIDCAuthURLRequest, q *WriteOptions) (*ACLOIDCAuthURLResponse, *WriteMeta, error) {
var resp ACLOIDCAuthURLResponse
wm, err := a.client.write("/v1/acl/oidc/auth-url", req, &resp, q)
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}
// CompleteAuth exchanges the OIDC provider token for a Nomad token with the
// appropriate claims attached.
func (a *ACLOIDC) CompleteAuth(req *ACLOIDCCompleteAuthRequest, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
var resp ACLToken
wm, err := a.client.write("/v1/acl/oidc/complete-auth", req, &resp, q)
if err != nil {
return nil, nil, err
}
return &resp, wm, nil
}
// ACLPolicyListStub is used to for listing ACL policies
type ACLPolicyListStub struct {
Name string
@ -666,6 +698,7 @@ type ACLAuthMethodConfig struct {
OIDCDiscoveryURL string
OIDCClientID string
OIDCClientSecret string
OIDCScopes []string
BoundAudiences []string
AllowedRedirectURIs []string
DiscoveryCaPem []string
@ -816,3 +849,50 @@ type ACLBindingRuleListStub struct {
CreateIndex uint64
ModifyIndex uint64
}
// ACLOIDCAuthURLRequest is the request to make when starting the OIDC
// authentication login flow.
type ACLOIDCAuthURLRequest struct {
// AuthMethodName is the OIDC auth-method to use. This is a required
// parameter.
AuthMethodName string
// RedirectURI is the URL that authorization should redirect to. This is a
// required parameter.
RedirectURI string
// ClientNonce is a randomly generated string to prevent replay attacks. It
// is up to the client to generate this and Go integrations should use the
// oidc.NewID function within the hashicorp/cap library.
ClientNonce string
}
// ACLOIDCAuthURLResponse is the response when starting the OIDC authentication
// login flow.
type ACLOIDCAuthURLResponse struct {
// AuthURL is URL to begin authorization and is where the user logging in
// should go.
AuthURL string
}
// ACLOIDCCompleteAuthRequest is the request object to begin completing the
// OIDC auth cycle after receiving the callback from the OIDC provider.
type ACLOIDCCompleteAuthRequest struct {
// AuthMethodName is the name of the auth method being used to login via
// OIDC. This will match AuthUrlArgs.AuthMethodName. This is a required
// parameter.
AuthMethodName string
// ClientNonce, State, and Code are provided from the parameters given to
// the redirect URL. These are all required parameters.
ClientNonce string
State string
Code string
// RedirectURI is the URL that authorization should redirect to. This is a
// required parameter.
RedirectURI string
}

View File

@ -829,3 +829,48 @@ func (s *HTTPServer) aclBindingRuleUpsertRequest(
}
return nil, nil
}
// ACLOIDCAuthURLRequest starts the OIDC login workflow.
func (s *HTTPServer) ACLOIDCAuthURLRequest(_ http.ResponseWriter, req *http.Request) (interface{}, error) {
// The endpoint only supports PUT or POST requests.
if req.Method != http.MethodPost && req.Method != http.MethodPut {
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
}
var args structs.ACLOIDCAuthURLRequest
s.parseWriteRequest(req, &args.WriteRequest)
if err := decodeBody(req, &args); err != nil {
return nil, CodedError(http.StatusBadRequest, err.Error())
}
var out structs.ACLOIDCAuthURLResponse
if err := s.agent.RPC(structs.ACLOIDCAuthURLRPCMethod, &args, &out); err != nil {
return nil, err
}
return out, nil
}
// ACLOIDCCompleteAuthRequest completes the OIDC login workflow.
func (s *HTTPServer) ACLOIDCCompleteAuthRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
// The endpoint only supports PUT or POST requests.
if req.Method != http.MethodPost && req.Method != http.MethodPut {
return nil, CodedError(http.StatusMethodNotAllowed, ErrInvalidMethod)
}
var args structs.ACLOIDCCompleteAuthRequest
s.parseWriteRequest(req, &args.WriteRequest)
if err := decodeBody(req, &args); err != nil {
return nil, CodedError(http.StatusBadRequest, err.Error())
}
var out structs.ACLOIDCCompleteAuthResponse
if err := s.agent.RPC(structs.ACLOIDCCompleteAuthRPCMethod, &args, &out); err != nil {
return nil, err
}
setIndex(resp, out.Index)
return out.ACLToken, nil
}

View File

@ -4,9 +4,11 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
capOIDC "github.com/hashicorp/cap/oidc"
"github.com/hashicorp/nomad/ci"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/nomad/mock"
@ -1213,10 +1215,10 @@ func TestHTTPServer_ACLAuthMethodSpecificRequest(t *testing.T) {
must.NoError(t, srv.server.State().UpsertACLAuthMethods(
20, []*structs.ACLAuthMethod{mockACLAuthMethod}))
url := "/v1/acl/auth-method/" + mockACLAuthMethod.Name
authMethodURL := "/v1/acl/auth-method/" + mockACLAuthMethod.Name
// Build the HTTP request.
req, err := http.NewRequest(http.MethodGet, url, nil)
req, err := http.NewRequest(http.MethodGet, authMethodURL, nil)
must.NoError(t, err)
respW := httptest.NewRecorder()
@ -1238,10 +1240,10 @@ func TestHTTPServer_ACLAuthMethodSpecificRequest(t *testing.T) {
must.NoError(t, srv.server.State().UpsertACLAuthMethods(
20, []*structs.ACLAuthMethod{mockACLAuthMethod}))
url := "/v1/acl/auth-method/" + mockACLAuthMethod.Name
authMethodURL := "/v1/acl/auth-method/" + mockACLAuthMethod.Name
// Build the HTTP request to read the auth-method.
req, err := http.NewRequest(http.MethodGet, url, nil)
req, err := http.NewRequest(http.MethodGet, authMethodURL, nil)
must.NoError(t, err)
respW := httptest.NewRecorder()
@ -1258,7 +1260,7 @@ func TestHTTPServer_ACLAuthMethodSpecificRequest(t *testing.T) {
mockACLAuthMethod.MaxTokenTTL = 3600 * time.Hour
mockACLAuthMethod.SetHash()
req, err = http.NewRequest(http.MethodPost, url, encodeReq(mockACLAuthMethod))
req, err = http.NewRequest(http.MethodPost, authMethodURL, encodeReq(mockACLAuthMethod))
must.NoError(t, err)
respW = httptest.NewRecorder()
@ -1270,7 +1272,7 @@ func TestHTTPServer_ACLAuthMethodSpecificRequest(t *testing.T) {
must.NoError(t, err)
// Delete the ACL auth-method.
req, err = http.NewRequest(http.MethodDelete, url, nil)
req, err = http.NewRequest(http.MethodDelete, authMethodURL, nil)
must.NoError(t, err)
respW = httptest.NewRecorder()
@ -1622,3 +1624,221 @@ func TestHTTPServer_ACLBindingRuleSpecificRequest(t *testing.T) {
})
}
}
func TestHTTPServer_ACLOIDCAuthURLRequest(t *testing.T) {
ci.Parallel(t)
testCases := []struct {
name string
testFn func(srv *TestAgent)
}{
{
name: "incorrect method",
testFn: func(testAgent *TestAgent) {
// Build the HTTP request.
req, err := http.NewRequest(http.MethodConnect, "/v1/acl/oidc/auth-url", nil)
must.NoError(t, err)
respW := httptest.NewRecorder()
// Send the HTTP request.
obj, err := testAgent.Server.ACLOIDCAuthURLRequest(respW, req)
must.Error(t, err)
must.StrContains(t, err.Error(), "Invalid method")
must.Nil(t, obj)
},
},
{
name: "success",
testFn: func(testAgent *TestAgent) {
// Set up the test OIDC provider.
oidcTestProvider := capOIDC.StartTestProvider(t)
defer oidcTestProvider.Stop()
// Generate and upsert an ACL auth method for use. Certain values must be
// taken from the cap OIDC provider just like real world use.
mockedAuthMethod := mock.ACLAuthMethod()
mockedAuthMethod.Config.AllowedRedirectURIs = []string{"http://127.0.0.1:4649/oidc/callback"}
mockedAuthMethod.Config.OIDCDiscoveryURL = oidcTestProvider.Addr()
mockedAuthMethod.Config.SigningAlgs = []string{"ES256"}
mockedAuthMethod.Config.DiscoveryCaPem = []string{oidcTestProvider.CACert()}
must.NoError(t, testAgent.server.State().UpsertACLAuthMethods(
10, []*structs.ACLAuthMethod{mockedAuthMethod}))
// Generate the request body.
requestBody := structs.ACLOIDCAuthURLRequest{
AuthMethodName: mockedAuthMethod.Name,
RedirectURI: mockedAuthMethod.Config.AllowedRedirectURIs[0],
ClientNonce: "fpSPuaodKevKfDU3IeXa",
WriteRequest: structs.WriteRequest{
Region: "global",
},
}
// Build the HTTP request.
req, err := http.NewRequest(http.MethodPost, "/v1/acl/oidc/auth-url", encodeReq(&requestBody))
must.NoError(t, err)
respW := httptest.NewRecorder()
// Send the HTTP request.
obj, err := testAgent.Server.ACLOIDCAuthURLRequest(respW, req)
must.NoError(t, err)
// The response URL comes encoded, so decode this and check we have each
// component we expect.
escapedURL, err := url.PathUnescape(obj.(structs.ACLOIDCAuthURLResponse).AuthURL)
must.NoError(t, err)
must.StrContains(t, escapedURL, "/authorize?client_id=mock")
must.StrContains(t, escapedURL, "&nonce=fpSPuaodKevKfDU3IeXa")
must.StrContains(t, escapedURL, "&redirect_uri=http://127.0.0.1:4649/oidc/callback")
must.StrContains(t, escapedURL, "&response_type=code")
must.StrContains(t, escapedURL, "&scope=openid")
must.StrContains(t, escapedURL, "&state=st_")
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
httpACLTest(t, nil, tc.testFn)
})
}
}
func TestHTTPServer_ACLOIDCCompleteAuthRequest(t *testing.T) {
ci.Parallel(t)
testCases := []struct {
name string
testFn func(srv *TestAgent)
}{
{
name: "incorrect method",
testFn: func(testAgent *TestAgent) {
// Build the HTTP request.
req, err := http.NewRequest(http.MethodConnect, "/v1/acl/oidc/complete-auth", nil)
must.NoError(t, err)
respW := httptest.NewRecorder()
// Send the HTTP request.
obj, err := testAgent.Server.ACLOIDCCompleteAuthRequest(respW, req)
must.Error(t, err)
must.StrContains(t, err.Error(), "Invalid method")
must.Nil(t, obj)
},
},
{
name: "success",
testFn: func(testAgent *TestAgent) {
// Set up the test OIDC provider.
oidcTestProvider := capOIDC.StartTestProvider(t)
defer oidcTestProvider.Stop()
oidcTestProvider.SetAllowedRedirectURIs([]string{"http://127.0.0.1:4649/oidc/callback"})
// Generate and upsert an ACL auth method for use. Certain values must be
// taken from the cap OIDC provider just like real world use.
mockedAuthMethod := mock.ACLAuthMethod()
mockedAuthMethod.Config.BoundAudiences = []string{"mock"}
mockedAuthMethod.Config.AllowedRedirectURIs = []string{"http://127.0.0.1:4649/oidc/callback"}
mockedAuthMethod.Config.OIDCDiscoveryURL = oidcTestProvider.Addr()
mockedAuthMethod.Config.SigningAlgs = []string{"ES256"}
mockedAuthMethod.Config.DiscoveryCaPem = []string{oidcTestProvider.CACert()}
mockedAuthMethod.Config.ClaimMappings = map[string]string{}
mockedAuthMethod.Config.ListClaimMappings = map[string]string{
"http://nomad.internal/roles": "roles",
"http://nomad.internal/policies": "policies",
}
must.NoError(t, testAgent.server.State().UpsertACLAuthMethods(
10, []*structs.ACLAuthMethod{mockedAuthMethod}))
// Set our custom data and some expected values, so we can make the RPC and
// use the test provider.
oidcTestProvider.SetExpectedAuthNonce("fpSPuaodKevKfDU3IeXa")
oidcTestProvider.SetExpectedAuthCode("codeABC")
oidcTestProvider.SetCustomAudience("mock")
oidcTestProvider.SetExpectedState("st_someweirdstateid")
oidcTestProvider.SetCustomClaims(map[string]interface{}{
"azp": "mock",
"http://nomad.internal/policies": []string{"engineering"},
"http://nomad.internal/roles": []string{"engineering"},
})
// Generate the request body.
requestBody := structs.ACLOIDCCompleteAuthRequest{
AuthMethodName: mockedAuthMethod.Name,
ClientNonce: "fpSPuaodKevKfDU3IeXa",
State: "st_someweirdstateid",
Code: "codeABC",
RedirectURI: mockedAuthMethod.Config.AllowedRedirectURIs[0],
WriteRequest: structs.WriteRequest{
Region: "global",
},
}
// Build the HTTP request.
req, err := http.NewRequest(http.MethodPost, "/v1/acl/oidc/complete-auth", encodeReq(&requestBody))
must.NoError(t, err)
respW := httptest.NewRecorder()
// Send the HTTP request.
_, err = testAgent.Server.ACLOIDCCompleteAuthRequest(respW, req)
must.ErrorContains(t, err, "no role or policy bindings matched")
// Upsert an ACL policy and role, so that we can reference this within our
// OIDC claims.
mockACLPolicy := mock.ACLPolicy()
must.NoError(t, testAgent.server.State().UpsertACLPolicies(
structs.MsgTypeTestSetup, 20, []*structs.ACLPolicy{mockACLPolicy}))
mockACLRole := mock.ACLRole()
mockACLRole.Policies = []*structs.ACLRolePolicyLink{{Name: mockACLPolicy.Name}}
must.NoError(t, testAgent.server.State().UpsertACLRoles(
structs.MsgTypeTestSetup, 30, []*structs.ACLRole{mockACLRole}, true))
// Generate and upsert two binding rules, so we can test both ACL Policy
// and Role claim mapping.
mockBindingRule1 := mock.ACLBindingRule()
mockBindingRule1.AuthMethod = mockedAuthMethod.Name
mockBindingRule1.BindType = structs.ACLBindingRuleBindTypePolicy
mockBindingRule1.Selector = "engineering in list.policies"
mockBindingRule1.BindName = mockACLPolicy.Name
mockBindingRule2 := mock.ACLBindingRule()
mockBindingRule2.AuthMethod = mockedAuthMethod.Name
mockBindingRule2.BindName = mockACLRole.Name
must.NoError(t, testAgent.server.State().UpsertACLBindingRules(
40, []*structs.ACLBindingRule{mockBindingRule1, mockBindingRule2}, true))
// Build the HTTP request.
req, err = http.NewRequest(http.MethodPost, "/v1/acl/oidc/complete-auth", encodeReq(&requestBody))
must.NoError(t, err)
respW = httptest.NewRecorder()
// Send the HTTP request.
obj, err := testAgent.Server.ACLOIDCCompleteAuthRequest(respW, req)
must.NoError(t, err)
aclTokenResp, ok := obj.(*structs.ACLToken)
must.True(t, ok)
must.NotNil(t, aclTokenResp)
must.Len(t, 1, aclTokenResp.Policies)
must.Eq(t, mockACLPolicy.Name, aclTokenResp.Policies[0])
must.Len(t, 1, aclTokenResp.Roles)
must.Eq(t, mockACLRole.Name, aclTokenResp.Roles[0].Name)
must.Eq(t, mockACLRole.ID, aclTokenResp.Roles[0].ID)
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
httpACLTest(t, nil, tc.testFn)
})
}
}

View File

@ -397,6 +397,10 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
s.mux.HandleFunc("/v1/acl/binding-rule", s.wrap(s.ACLBindingRuleRequest))
s.mux.HandleFunc("/v1/acl/binding-rule/", s.wrap(s.ACLBindingRuleSpecificRequest))
// Register out ACL OIDC SSO provider handlers.
s.mux.HandleFunc("/v1/acl/oidc/auth-url", s.wrap(s.ACLOIDCAuthURLRequest))
s.mux.HandleFunc("/v1/acl/oidc/complete-auth", s.wrap(s.ACLOIDCCompleteAuthRequest))
s.mux.Handle("/v1/client/fs/", wrapCORS(s.wrap(s.FsRequest)))
s.mux.HandleFunc("/v1/client/gc", s.wrap(s.ClientGCRequest))
s.mux.Handle("/v1/client/stats", wrapCORS(s.wrap(s.ClientStatsRequest)))

View File

@ -0,0 +1,181 @@
package apitests
import (
"net/url"
"testing"
"time"
capOIDC "github.com/hashicorp/cap/oidc"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/ci"
"github.com/shoenig/test/must"
)
func TestACLOIDC_GetAuthURL(t *testing.T) {
ci.Parallel(t)
testClient, testServer, _ := makeACLClient(t, nil, nil)
defer testServer.Stop()
// Set up the test OIDC provider.
oidcTestProvider := capOIDC.StartTestProvider(t)
defer oidcTestProvider.Stop()
oidcTestProvider.SetAllowedRedirectURIs([]string{"http://127.0.0.1:4649/oidc/callback"})
// Generate and upsert an ACL auth method for use. Certain values must be
// taken from the cap OIDC provider just like real world use.
mockedAuthMethod := api.ACLAuthMethod{
Name: "api-test-auth-method",
Type: api.ACLAuthMethodTypeOIDC,
TokenLocality: api.ACLAuthMethodTokenLocalityGlobal,
MaxTokenTTL: 10 * time.Hour,
Default: true,
Config: &api.ACLAuthMethodConfig{
OIDCDiscoveryURL: oidcTestProvider.Addr(),
OIDCClientID: "mock",
OIDCClientSecret: "verysecretsecret",
BoundAudiences: []string{"mock"},
AllowedRedirectURIs: []string{"http://127.0.0.1:4649/oidc/callback"},
DiscoveryCaPem: []string{oidcTestProvider.CACert()},
SigningAlgs: []string{"ES256"},
ClaimMappings: map[string]string{"foo": "bar"},
ListClaimMappings: map[string]string{"foo": "bar"},
},
}
createdAuthMethod, writeMeta, err := testClient.ACLAuthMethods().Create(&mockedAuthMethod, nil)
must.NoError(t, err)
must.NotNil(t, createdAuthMethod)
assertWriteMeta(t, writeMeta)
// Generate and make the request.
authURLRequest := api.ACLOIDCAuthURLRequest{
AuthMethodName: createdAuthMethod.Name,
RedirectURI: createdAuthMethod.Config.AllowedRedirectURIs[0],
ClientNonce: "fpSPuaodKevKfDU3IeXb",
}
authURLResp, _, err := testClient.ACLOIDC().GetAuthURL(&authURLRequest, nil)
must.NoError(t, err)
// The response URL comes encoded, so decode this and check we have each
// component we expect.
escapedURL, err := url.PathUnescape(authURLResp.AuthURL)
must.NoError(t, err)
must.StrContains(t, escapedURL, "/authorize?client_id=mock")
must.StrContains(t, escapedURL, "&nonce=fpSPuaodKevKfDU3IeXb")
must.StrContains(t, escapedURL, "&redirect_uri=http://127.0.0.1:4649/oidc/callback")
must.StrContains(t, escapedURL, "&response_type=code")
must.StrContains(t, escapedURL, "&scope=openid")
must.StrContains(t, escapedURL, "&state=st_")
}
func TestACLOIDC_CompleteAuth(t *testing.T) {
ci.Parallel(t)
testClient, testServer, _ := makeACLClient(t, nil, nil)
defer testServer.Stop()
// Set up the test OIDC provider.
oidcTestProvider := capOIDC.StartTestProvider(t)
defer oidcTestProvider.Stop()
oidcTestProvider.SetAllowedRedirectURIs([]string{"http://127.0.0.1:4649/oidc/callback"})
// Generate and upsert an ACL auth method for use. Certain values must be
// taken from the cap OIDC provider just like real world use.
mockedAuthMethod := api.ACLAuthMethod{
Name: "api-test-auth-method",
Type: api.ACLAuthMethodTypeOIDC,
TokenLocality: api.ACLAuthMethodTokenLocalityGlobal,
MaxTokenTTL: 10 * time.Hour,
Default: true,
Config: &api.ACLAuthMethodConfig{
OIDCDiscoveryURL: oidcTestProvider.Addr(),
OIDCClientID: "mock",
OIDCClientSecret: "verysecretsecret",
BoundAudiences: []string{"mock"},
AllowedRedirectURIs: []string{"http://127.0.0.1:4649/oidc/callback"},
DiscoveryCaPem: []string{oidcTestProvider.CACert()},
SigningAlgs: []string{"ES256"},
ClaimMappings: map[string]string{},
ListClaimMappings: map[string]string{
"http://nomad.internal/roles": "roles",
"http://nomad.internal/policies": "policies",
},
},
}
createdAuthMethod, writeMeta, err := testClient.ACLAuthMethods().Create(&mockedAuthMethod, nil)
must.NoError(t, err)
must.NotNil(t, createdAuthMethod)
assertWriteMeta(t, writeMeta)
// Set our custom data and some expected values, so we can make the call
// and use the test provider.
oidcTestProvider.SetExpectedAuthNonce("fpSPuaodKevKfDU3IeXb")
oidcTestProvider.SetExpectedAuthCode("codeABC")
oidcTestProvider.SetCustomAudience("mock")
oidcTestProvider.SetExpectedState("st_someweirdstateid")
oidcTestProvider.SetCustomClaims(map[string]interface{}{
"azp": "mock",
"http://nomad.internal/policies": []string{"engineering"},
"http://nomad.internal/roles": []string{"engineering"},
})
// Upsert an ACL policy and role, so that we can reference this within our
// OIDC claims.
mockedACLPolicy := api.ACLPolicy{
Name: "api-oidc-login-test",
Rules: `namespace "default" { policy = "write"}`,
}
_, err = testClient.ACLPolicies().Upsert(&mockedACLPolicy, nil)
must.NoError(t, err)
mockedACLRole := api.ACLRole{
Name: "api-oidc-login-test",
Policies: []*api.ACLRolePolicyLink{{Name: mockedACLPolicy.Name}},
}
createRoleResp, _, err := testClient.ACLRoles().Create(&mockedACLRole, nil)
must.NoError(t, err)
must.NotNil(t, createRoleResp)
// Generate and upsert two binding rules, so we can test both ACL Policy
// and Role claim mapping.
mockedBindingRule1 := api.ACLBindingRule{
AuthMethod: mockedAuthMethod.Name,
Selector: "engineering in list.policies",
BindType: api.ACLBindingRuleBindTypePolicy,
BindName: mockedACLPolicy.Name,
}
createBindingRole1Resp, _, err := testClient.ACLBindingRules().Create(&mockedBindingRule1, nil)
must.NoError(t, err)
must.NotNil(t, createBindingRole1Resp)
mockedBindingRule2 := api.ACLBindingRule{
AuthMethod: mockedAuthMethod.Name,
Selector: "engineering in list.roles",
BindType: api.ACLBindingRuleBindTypeRole,
BindName: mockedACLRole.Name,
}
createBindingRole2Resp, _, err := testClient.ACLBindingRules().Create(&mockedBindingRule2, nil)
must.NoError(t, err)
must.NotNil(t, createBindingRole2Resp)
// Generate and make the request.
authURLRequest := api.ACLOIDCCompleteAuthRequest{
AuthMethodName: createdAuthMethod.Name,
RedirectURI: createdAuthMethod.Config.AllowedRedirectURIs[0],
ClientNonce: "fpSPuaodKevKfDU3IeXb",
State: "st_someweirdstateid",
Code: "codeABC",
}
completeAuthResp, _, err := testClient.ACLOIDC().CompleteAuth(&authURLRequest, nil)
must.NoError(t, err)
must.NotNil(t, completeAuthResp)
must.Len(t, 1, completeAuthResp.Policies)
must.Eq(t, mockedACLPolicy.Name, completeAuthResp.Policies[0])
must.Len(t, 1, completeAuthResp.Roles)
must.Eq(t, mockedACLRole.Name, completeAuthResp.Roles[0].Name)
must.Eq(t, createRoleResp.ID, completeAuthResp.Roles[0].ID)
}