650 lines
27 KiB
Go
Raw Normal View History

//go:build integration
package saml_test
import (
"context"
"net/url"
"regexp"
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/crewjam/saml"
"github.com/crewjam/saml/samlsp"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
saml_pb "github.com/zitadel/zitadel/pkg/grpc/saml/v2"
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
)
func TestServer_GetSAMLRequest(t *testing.T) {
idpMetadata, err := Instance.GetSAMLIDPMetadata()
require.NoError(t, err)
acsRedirect := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[0]
acsPost := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[1]
_, _, spMiddlewareRedirect := createSAMLApplication(CTX, t, idpMetadata, saml.HTTPRedirectBinding, false, false)
_, _, spMiddlewarePost := createSAMLApplication(CTX, t, idpMetadata, saml.HTTPPostBinding, false, false)
tests := []struct {
name string
dep func() (time.Time, string, error)
wantErr bool
}{
{
name: "Not found",
dep: func() (time.Time, string, error) {
return time.Time{}, "123", nil
},
wantErr: true,
},
{
name: "success, redirect binding",
dep: func() (time.Time, string, error) {
return Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
},
},
{
name: "success, post binding",
dep: func() (time.Time, string, error) {
return Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
creationTime, authRequestID, err := tt.dep()
require.NoError(t, err)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
got, err := Client.GetSAMLRequest(CTX, &saml_pb.GetSAMLRequestRequest{
SamlRequestId: authRequestID,
})
if tt.wantErr {
assert.Error(ttt, err)
return
}
assert.NoError(ttt, err)
authRequest := got.GetSamlRequest()
assert.NotNil(ttt, authRequest)
assert.Equal(ttt, authRequestID, authRequest.GetId())
assert.WithinRange(ttt, authRequest.GetCreationDate().AsTime(), creationTime.Add(-time.Second), creationTime.Add(time.Second))
}, retryDuration, tick, "timeout waiting for expected saml request result")
})
}
}
func TestServer_CreateResponse(t *testing.T) {
idpMetadata, err := Instance.GetSAMLIDPMetadata()
require.NoError(t, err)
acsRedirect := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[0]
acsPost := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[1]
_, rootURLPost, spMiddlewarePost := createSAMLApplication(CTX, t, idpMetadata, saml.HTTPPostBinding, false, false)
_, rootURLRedirect, spMiddlewareRedirect := createSAMLApplication(CTX, t, idpMetadata, saml.HTTPRedirectBinding, false, false)
sessionResp := createSession(CTX, t, Instance.Users[integration.UserTypeOrgOwner].ID)
tests := []struct {
name string
req *saml_pb.CreateResponseRequest
AuthError string
want *saml_pb.CreateResponseResponse
wantURL *url.URL
wantErr bool
}{
{
name: "Not found",
req: &saml_pb.CreateResponseRequest{
SamlRequestId: "123",
ResponseKind: &saml_pb.CreateResponseRequest_Session{
Session: &saml_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
},
},
},
wantErr: true,
},
{
name: "session not found",
req: &saml_pb.CreateResponseRequest{
SamlRequestId: func() string {
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
require.NoError(t, err)
return authRequestID
}(),
ResponseKind: &saml_pb.CreateResponseRequest_Session{
Session: &saml_pb.Session{
SessionId: "foo",
SessionToken: "bar",
},
},
},
wantErr: true,
},
{
name: "session token invalid",
req: &saml_pb.CreateResponseRequest{
SamlRequestId: func() string {
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
require.NoError(t, err)
return authRequestID
}(),
ResponseKind: &saml_pb.CreateResponseRequest_Session{
Session: &saml_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: "bar",
},
},
},
wantErr: true,
},
{
name: "fail callback, post",
req: &saml_pb.CreateResponseRequest{
SamlRequestId: func() string {
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
require.NoError(t, err)
return authRequestID
}(),
ResponseKind: &saml_pb.CreateResponseRequest_Error{
Error: &saml_pb.AuthorizationError{
Error: saml_pb.ErrorReason_ERROR_REASON_REQUEST_DENIED,
ErrorDescription: gu.Ptr("nope"),
},
},
},
want: &saml_pb.CreateResponseResponse{
Url: regexp.QuoteMeta(`https://` + rootURLPost + `/saml/acs`),
Binding: &saml_pb.CreateResponseResponse_Post{Post: &saml_pb.PostResponse{
RelayState: "notempty",
SamlResponse: "notempty",
}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
wantErr: false,
},
{
name: "fail callback, post, already failed",
req: &saml_pb.CreateResponseRequest{
SamlRequestId: func() string {
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
require.NoError(t, err)
Instance.FailSAMLAuthRequest(CTX, authRequestID, saml_pb.ErrorReason_ERROR_REASON_AUTH_N_FAILED)
return authRequestID
}(),
ResponseKind: &saml_pb.CreateResponseRequest_Error{
Error: &saml_pb.AuthorizationError{
Error: saml_pb.ErrorReason_ERROR_REASON_REQUEST_DENIED,
ErrorDescription: gu.Ptr("nope"),
},
},
},
wantErr: true,
},
{
name: "fail callback, redirect",
req: &saml_pb.CreateResponseRequest{
SamlRequestId: func() string {
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
require.NoError(t, err)
return authRequestID
}(),
ResponseKind: &saml_pb.CreateResponseRequest_Error{
Error: &saml_pb.AuthorizationError{
Error: saml_pb.ErrorReason_ERROR_REASON_REQUEST_DENIED,
ErrorDescription: gu.Ptr("nope"),
},
},
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/` + rootURLRedirect + `\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
wantErr: false,
},
{
name: "callback, redirect",
req: &saml_pb.CreateResponseRequest{
SamlRequestId: func() string {
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewareRedirect, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, gofakeit.BitcoinAddress(), saml.HTTPRedirectBinding)
require.NoError(t, err)
return authRequestID
}(),
ResponseKind: &saml_pb.CreateResponseRequest_Session{
Session: &saml_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
},
},
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/` + rootURLRedirect + `\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
wantErr: false,
},
{
name: "callback, post",
req: &saml_pb.CreateResponseRequest{
SamlRequestId: func() string {
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
require.NoError(t, err)
return authRequestID
}(),
ResponseKind: &saml_pb.CreateResponseRequest_Session{
Session: &saml_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
},
},
},
want: &saml_pb.CreateResponseResponse{
Url: regexp.QuoteMeta(`https://` + rootURLPost + `/saml/acs`),
Binding: &saml_pb.CreateResponseResponse_Post{Post: &saml_pb.PostResponse{
RelayState: "notempty",
SamlResponse: "notempty",
}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
wantErr: false,
},
{
name: "callback, post",
req: &saml_pb.CreateResponseRequest{
SamlRequestId: func() string {
_, authRequestID, err := Instance.CreateSAMLAuthRequest(spMiddlewarePost, Instance.Users[integration.UserTypeOrgOwner].ID, acsPost, gofakeit.BitcoinAddress(), saml.HTTPPostBinding)
require.NoError(t, err)
Instance.SuccessfulSAMLAuthRequest(CTX, Instance.Users[integration.UserTypeOrgOwner].ID, authRequestID)
return authRequestID
}(),
ResponseKind: &saml_pb.CreateResponseRequest_Session{
Session: &saml_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
},
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.CreateResponse(CTX, tt.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
if tt.want != nil {
assert.Regexp(t, regexp.MustCompile(tt.want.Url), got.GetUrl())
if tt.want.GetPost() != nil {
assert.NotEmpty(t, got.GetPost().GetRelayState())
assert.NotEmpty(t, got.GetPost().GetSamlResponse())
}
if tt.want.GetRedirect() != nil {
assert.NotNil(t, got.GetRedirect())
}
}
})
}
}
func TestServer_CreateResponse_Permission(t *testing.T) {
idpMetadata, err := Instance.GetSAMLIDPMetadata()
require.NoError(t, err)
acsRedirect := idpMetadata.IDPSSODescriptors[0].SingleSignOnServices[0]
tests := []struct {
name string
dep func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest
want *saml_pb.CreateResponseResponse
wantURL *url.URL
wantErr bool
}{
{
name: "usergrant to project and different resourceowner with different project grant",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
projectID2, _, _ := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID2, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
wantErr: true,
},
{
name: "usergrant to project and different resourceowner with project grant",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permission-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
wantErr: false,
},
{
name: "usergrant to project grant and different resourceowner with project grant",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permission-"+gofakeit.AppName(), gofakeit.Email())
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
wantErr: false,
},
{
name: "no usergrant and different resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
wantErr: true,
},
{
name: "no usergrant and same resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
user := Instance.CreateHumanUser(ctx)
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
wantErr: true,
},
{
name: "usergrant and different resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
wantErr: true,
},
{
name: "usergrant and same resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, true)
user := Instance.CreateHumanUser(ctx)
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
{
name: "projectRoleCheck, usergrant and same resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
user := Instance.CreateHumanUser(ctx)
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
{
name: "projectRoleCheck, no usergrant and same resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
user := Instance.CreateHumanUser(ctx)
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
wantErr: true,
},
{
name: "projectRoleCheck, usergrant and different resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectUserGrant(t, ctx, projectID, user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
{
name: "projectRoleCheck, no usergrant and different resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
wantErr: true,
},
{
name: "projectRoleCheck, usergrant on project grant and different resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
orgResp := Instance.CreateOrganization(ctx, "saml-permissison-"+gofakeit.AppName(), gofakeit.Email())
projectGrantResp := Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
Instance.CreateProjectGrantUserGrant(ctx, orgResp.GetOrganizationId(), projectID, projectGrantResp.GetGrantId(), user.GetUserId())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
{
name: "projectRoleCheck, no usergrant on project grant and different resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, true, false)
orgResp := Instance.CreateOrganization(ctx, "saml-permissison-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
wantErr: true,
},
{
name: "hasProjectCheck, same resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, false, true)
user := Instance.CreateHumanUser(ctx)
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
{
name: "hasProjectCheck, different resourceowner",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
_, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, false, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permisison-"+gofakeit.AppName(), gofakeit.Email())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
wantErr: true,
},
{
name: "hasProjectCheck, different resourceowner with project grant",
dep: func(ctx context.Context, t *testing.T) *saml_pb.CreateResponseRequest {
projectID, _, sp := createSAMLApplication(ctx, t, idpMetadata, saml.HTTPRedirectBinding, false, true)
orgResp := Instance.CreateOrganization(ctx, "saml-permissison-"+gofakeit.AppName(), gofakeit.Email())
Instance.CreateProjectGrant(ctx, projectID, orgResp.GetOrganizationId())
user := Instance.CreateHumanUserVerified(ctx, orgResp.GetOrganizationId(), gofakeit.Email(), gofakeit.Phone())
return createSessionAndSmlRequestForCallback(ctx, t, sp, Instance.Users[integration.UserTypeOrgOwner].ID, acsRedirect, user.GetUserId(), saml.HTTPRedirectBinding)
},
want: &saml_pb.CreateResponseResponse{
Url: `https:\/\/(.*)\/saml\/acs\?RelayState=(.*)&SAMLResponse=(.*)&SigAlg=(.*)&Signature=(.*)`,
Binding: &saml_pb.CreateResponseResponse_Redirect{Redirect: &saml_pb.RedirectResponse{}},
Details: &object.Details{
ChangeDate: timestamppb.Now(),
ResourceOwner: Instance.ID(),
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
req := tt.dep(IAMCTX, t)
got, err := Client.CreateResponse(CTX, req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
integration.AssertDetails(t, tt.want, got)
if tt.want != nil {
assert.Regexp(t, regexp.MustCompile(tt.want.Url), got.GetUrl())
if tt.want.GetPost() != nil {
assert.NotEmpty(t, got.GetPost().GetRelayState())
assert.NotEmpty(t, got.GetPost().GetSamlResponse())
}
if tt.want.GetRedirect() != nil {
assert.NotNil(t, got.GetRedirect())
}
}
})
}
}
func createSession(ctx context.Context, t *testing.T, userID string) *session.CreateSessionResponse {
sessionResp, err := Instance.Client.SessionV2.CreateSession(ctx, &session.CreateSessionRequest{
Checks: &session.Checks{
User: &session.CheckUser{
Search: &session.CheckUser_UserId{
UserId: userID,
},
},
},
})
require.NoError(t, err)
return sessionResp
}
func createSessionAndSmlRequestForCallback(ctx context.Context, t *testing.T, sp *samlsp.Middleware, loginClient string, acsRedirect saml.Endpoint, userID, binding string) *saml_pb.CreateResponseRequest {
_, authRequestID, err := Instance.CreateSAMLAuthRequest(sp, loginClient, acsRedirect, gofakeit.BitcoinAddress(), binding)
require.NoError(t, err)
sessionResp := createSession(ctx, t, userID)
return &saml_pb.CreateResponseRequest{
SamlRequestId: authRequestID,
ResponseKind: &saml_pb.CreateResponseRequest_Session{
Session: &saml_pb.Session{
SessionId: sessionResp.GetSessionId(),
SessionToken: sessionResp.GetSessionToken(),
},
},
}
}
func createSAMLSP(t *testing.T, idpMetadata *saml.EntityDescriptor, binding string) (string, *samlsp.Middleware) {
rootURL := "example." + gofakeit.DomainName()
spMiddleware, err := integration.CreateSAMLSP("https://"+rootURL, idpMetadata, binding)
require.NoError(t, err)
return rootURL, spMiddleware
}
func createSAMLApplication(ctx context.Context, t *testing.T, idpMetadata *saml.EntityDescriptor, binding string, projectRoleCheck, hasProjectCheck bool) (string, string, *samlsp.Middleware) {
project, err := Instance.CreateProjectWithPermissionCheck(ctx, projectRoleCheck, hasProjectCheck)
require.NoError(t, err)
rootURL, sp := createSAMLSP(t, idpMetadata, binding)
_, err = Instance.CreateSAMLClient(ctx, project.GetId(), sp)
require.NoError(t, err)
return project.GetId(), rootURL, sp
}