diff --git a/client/client.go b/client/client.go index 16565bd..ea9ad04 100644 --- a/client/client.go +++ b/client/client.go @@ -345,6 +345,50 @@ func (a *Client) IssueFromOIDCStep2(ctx context.Context, code string, state stri return a.sendRequest(subctx, issueRequest) } +// IssueFromSAMLStep1 issues a Midgard jwt from a SAML provider. This is performing the first step to +// validate the issue requests and OIDC provider. It will return the OIDC auth endpoint +func (a *Client) IssueFromSAMLStep1(ctx context.Context, namespace string, provider string, redirectURL string) (string, error) { + + issueRequest := gaia.NewIssue() + issueRequest.Metadata = map[string]interface{}{ + "namespace": namespace, + "SAMLProviderName": provider, + "redirectURL": redirectURL, + } + issueRequest.Realm = gaia.IssueRealmSAML + + span, subctx := opentracing.StartSpanFromContext(ctx, "midgardlib.client.issue.saml.step1") + defer span.Finish() + + return a.sendRequest(subctx, issueRequest) +} + +// IssueFromSAMLStep2 issues a Midgard jwt from a SAML provider. This is performing the second step to +// to exchange the code for a Midgard HWT. +func (a *Client) IssueFromSAMLStep2(ctx context.Context, response string, state string, validity time.Duration, options ...Option) (string, error) { + + opts := issueOpts{} + for _, opt := range options { + opt(&opts) + } + + issueRequest := gaia.NewIssue() + issueRequest.Metadata = map[string]interface{}{ + "SAMLResponse": response, + "relayState": state, + } + issueRequest.Realm = gaia.IssueRealmSAML + issueRequest.Validity = validity.String() + issueRequest.Quota = opts.quota + issueRequest.Opaque = opts.opaque + issueRequest.Audience = opts.audience + + span, subctx := opentracing.StartSpanFromContext(ctx, "midgardlib.client.issue.saml.step2") + defer span.Finish() + + return a.sendRequest(subctx, issueRequest) +} + // IssueFromAzureIdentityToken issues a Midgard jwt from a signed Azure identity document for the given validity duration. func (a *Client) IssueFromAzureIdentityToken(ctx context.Context, token string, validity time.Duration, options ...Option) (string, error) { diff --git a/client/client_test.go b/client/client_test.go index 5609e9c..f2890a6 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -481,6 +481,47 @@ func TestClient_IssueFromAzureIdentityToken(t *testing.T) { func TestClient_IssueFromOIDCStep1(t *testing.T) { + Convey("Given I have a client and a fake working server", t, func() { + + expectedRequest := gaia.NewIssue() + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if err := json.NewDecoder(r.Body).Decode(expectedRequest); err != nil { + panic(err) + } + + w.Header().Set("Location", "http://laba") + w.WriteHeader(http.StatusFound) + + })) + defer ts.Close() + + cl := NewClient(ts.URL) + + Convey("When I call IssueFromOIDCStep1(", func() { + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + url, err := cl.IssueFromOIDCStep1(ctx, "aporeto", "okta", "http://ici") + + Convey("Then err should be nil", func() { + So(err, ShouldBeNil) + }) + + Convey("Then the issue request should be correct", func() { + So(expectedRequest.Realm, ShouldEqual, "OIDC") + }) + + Convey("Then url should be correct", func() { + So(url, ShouldEqual, "http://laba") + }) + }) + }) +} + +func TestClient_IssueFromOIDCStep2(t *testing.T) { + Convey("Given I have a client and a fake working server", t, func() { expectedRequest := gaia.NewIssue() @@ -521,7 +562,7 @@ func TestClient_IssueFromOIDCStep1(t *testing.T) { }) } -func TestClient_IssueFromOIDCStep2(t *testing.T) { +func TestClient_IssueFromSAMLStep1(t *testing.T) { Convey("Given I have a client and a fake working server", t, func() { @@ -540,19 +581,19 @@ func TestClient_IssueFromOIDCStep2(t *testing.T) { cl := NewClient(ts.URL) - Convey("When I call IssueFromOIDCStep1(", func() { + Convey("When I call IssueFromSAMLStep1(", func() { ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) defer cancel() - url, err := cl.IssueFromOIDCStep1(ctx, "aporeto", "okta", "http://ici") + url, err := cl.IssueFromSAMLStep1(ctx, "aporeto", "okta", "http://ici") Convey("Then err should be nil", func() { So(err, ShouldBeNil) }) Convey("Then the issue request should be correct", func() { - So(expectedRequest.Realm, ShouldEqual, "OIDC") + So(expectedRequest.Realm, ShouldEqual, "SAML") }) Convey("Then url should be correct", func() { @@ -562,6 +603,48 @@ func TestClient_IssueFromOIDCStep2(t *testing.T) { }) } +func TestClient_IssueFromSAMLStep2(t *testing.T) { + + Convey("Given I have a client and a fake working server", t, func() { + + expectedRequest := gaia.NewIssue() + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if err := json.NewDecoder(r.Body).Decode(expectedRequest); err != nil { + panic(err) + } + + fmt.Fprintln(w, `{"data": "","realm": "saml","token": "token"}`) + + })) + defer ts.Close() + + cl := NewClient(ts.URL) + + Convey("When I call IssueFromSAMLStep2", func() { + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + + token, err := cl.IssueFromSAMLStep2(ctx, "response", "state", 1*time.Minute, OptQuota(1)) + + Convey("Then err should be nil", func() { + So(err, ShouldBeNil) + }) + + Convey("Then the issue request should be correct", func() { + So(expectedRequest.Realm, ShouldEqual, "SAML") + So(expectedRequest.Metadata["SAMLResponse"], ShouldEqual, "response") + So(expectedRequest.Metadata["relayState"], ShouldEqual, "state") + }) + + Convey("Then token should be correct", func() { + So(token, ShouldEqual, "token") + }) + }) + }) +} + func TestClient_IssueFromAWSSecurityToken(t *testing.T) { Convey("Given I have a fake working server", t, func() {