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() { diff --git a/go.mod b/go.mod index 4d9bb38..1eb7b69 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module go.aporeto.io/midgard-lib go 1.12 require ( - go.aporeto.io/elemental v1.85.0 - go.aporeto.io/gaia v1.36.0 - go.aporeto.io/tg v1.24.0 + go.aporeto.io/elemental v1.88.0 + go.aporeto.io/gaia v1.48.0 + go.aporeto.io/tg v1.25.0 ) require ( diff --git a/go.sum b/go.sum index 603e7f6..5157091 100644 --- a/go.sum +++ b/go.sum @@ -163,13 +163,13 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1: github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -go.aporeto.io/elemental v1.85.0 h1:n3rRy2yu9Ewqh49oSAn/j0QC/aBY8DaXsxXYe53qMZE= -go.aporeto.io/elemental v1.85.0/go.mod h1:JhzKhBRoQqHnBcbdVdaDFW8BaZzH6nXssHxTu3qCbGs= -go.aporeto.io/gaia v1.36.0 h1:P8BUMhlo95cpktolY+mM/KznKKQgYqIQC+qe6ebBzjU= -go.aporeto.io/gaia v1.36.0/go.mod h1:noTauKCelgsQD7SK/RtrfGkpXq54QLBNtcMY5N4ajL4= -go.aporeto.io/regolithe v1.39.0/go.mod h1:n5Wi2kGXJ+eZe4fUTiVZ++RlgIo3DKiG71I7TV1Pfz4= -go.aporeto.io/tg v1.24.0 h1:hUr+PszwF0qvLZi7+AftV/0yvMF9zo8ZIslW2napmso= -go.aporeto.io/tg v1.24.0/go.mod h1:t9IL2iIzj9902gGj+nc/rsowvDiTF5oQvRn+2srFGhg= +go.aporeto.io/elemental v1.88.0 h1:pllC+ft4H6dbIwXdaussTuk9UyBGAYHL6ajU7RHkBGU= +go.aporeto.io/elemental v1.88.0/go.mod h1:l3K+eg8n3Z5KbwPl0E9WunoEP+d1hSZpiapT6S3Axe0= +go.aporeto.io/gaia v1.48.0 h1:AyI0+tTaqp94NfrQ5FlXgN3Ds4PhkpCyVRGKmC78PSM= +go.aporeto.io/gaia v1.48.0/go.mod h1:eP6gp0XDINRSKAF+PvVUJAxNZ+4SxF2WcoN4+8ab0Go= +go.aporeto.io/regolithe v1.40.0/go.mod h1:n5Wi2kGXJ+eZe4fUTiVZ++RlgIo3DKiG71I7TV1Pfz4= +go.aporeto.io/tg v1.25.0 h1:KhajW6SU2FN4w/WNvE/LAwyYEt+erYnlUK2Kpyaf2mg= +go.aporeto.io/tg v1.25.0/go.mod h1:t9IL2iIzj9902gGj+nc/rsowvDiTF5oQvRn+2srFGhg= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=