diff --git a/pkg/restapi/gnap/operations.go b/pkg/restapi/gnap/operations.go index 994c636..1624f91 100644 --- a/pkg/restapi/gnap/operations.go +++ b/pkg/restapi/gnap/operations.go @@ -119,7 +119,7 @@ func (o *Operation) authRequestHandler(w http.ResponseWriter, req *http.Request) func (o *Operation) interactHandler(w http.ResponseWriter, req *http.Request) { // TODO validate txn_id // redirect to UI - http.Redirect(w, req, o.uiEndpoint, http.StatusFound) + http.Redirect(w, req, o.uiEndpoint+"/sign-up", http.StatusFound) } func (o *Operation) authContinueHandler(w http.ResponseWriter, req *http.Request) { diff --git a/test/bdd/fixtures/auth-rest/docker-compose.yml b/test/bdd/fixtures/auth-rest/docker-compose.yml index ccefc7a..4b1ffa2 100644 --- a/test/bdd/fixtures/auth-rest/docker-compose.yml +++ b/test/bdd/fixtures/auth-rest/docker-compose.yml @@ -20,7 +20,7 @@ services: - AUTH_REST_DATABASE_TYPE=mongodb - AUTH_REST_DATABASE_URL=mongodb://mongodb.example.com:27017 - AUTH_REST_DATABASE_PREFIX=authrest_ - - AUTH_REST_OIDC_CALLBACK=https://localhost:8070/oauth2/callback # https://github.com/trustbloc/auth/issues/13 + - AUTH_REST_OIDC_CALLBACK=https://auth.trustbloc.local:8070/oauth2/callback # https://github.com/trustbloc/auth/issues/13 - AUTH_REST_OIDC_PROVIDERS_CONFIG=/etc/oidc-config/providers.yaml - AUTH_REST_SDS_DOCS_URL=https://TODO.docs.sds.org # onboard user: https://github.com/trustbloc/auth/issues/38 - AUTH_REST_SDS_OPSKEYS_URL=https://TODO.keys.sds.org @@ -58,8 +58,8 @@ services: environment: - DSN=mysql://authresthydra:authresthydra-secret-pw@tcp(mysql:3306)/authresthydra?max_conns=20&max_idle_conns=4 - URLS_SELF_ISSUER=https://localhost:4444/ - - URLS_CONSENT=https://localhost:8070/hydra/consent - - URLS_LOGIN=https://localhost:8070/hydra/login + - URLS_CONSENT=https://auth.trustbloc.local:8070/hydra/consent + - URLS_LOGIN=https://auth.trustbloc.local:8070/hydra/login - SECRETS_SYSTEM=testSecretsSystem - OIDC_SUBJECT_TYPES_SUPPORTED=public - OIDC_SUBJECT_TYPE_PAIRWISE_SALT=testSecretsSystem diff --git a/test/bdd/fixtures/auth-rest/hydra-config/thirdparty_hydra_configure.sh b/test/bdd/fixtures/auth-rest/hydra-config/thirdparty_hydra_configure.sh index 3ca5cc7..60295d7 100755 --- a/test/bdd/fixtures/auth-rest/hydra-config/thirdparty_hydra_configure.sh +++ b/test/bdd/fixtures/auth-rest/hydra-config/thirdparty_hydra_configure.sh @@ -16,7 +16,7 @@ hydra clients create \ --response-types code,id_token \ --scope openid,profile,email \ --skip-tls-verify \ - --callbacks https://localhost:8070/oauth2/callback + --callbacks https://auth.trustbloc.local:8070/oauth2/callback # TODO it would be great to check the exit status of the hydra command # https://github.com/trustbloc/auth/issues/67 echo "Finished creating oidc client!" @@ -32,7 +32,7 @@ hydra clients create \ --response-types code,id_token \ --scope openid,profile,email \ --skip-tls-verify \ - --callbacks https://localhost:8070/oauth2/callback + --callbacks https://auth.trustbloc.local:8070/oauth2/callback # TODO it would be great to check the exit status of the hydra command # https://github.com/trustbloc/auth/issues/67 echo "Finished creating oidc client for gnap flow!" diff --git a/test/bdd/mock/loginconsent/go.mod b/test/bdd/mock/loginconsent/go.mod index ad7e732..c9157f4 100644 --- a/test/bdd/mock/loginconsent/go.mod +++ b/test/bdd/mock/loginconsent/go.mod @@ -7,6 +7,7 @@ module mock_server go 1.17 require ( + github.com/google/uuid v1.1.2 github.com/gorilla/mux v1.8.0 github.com/hyperledger/aries-framework-go/component/storageutil v0.0.0-20210807121559-b41545a4f1e8 github.com/hyperledger/aries-framework-go/spi v0.0.0-20210807121559-b41545a4f1e8 diff --git a/test/bdd/mock/loginconsent/server.go b/test/bdd/mock/loginconsent/server.go index df00430..cb7c550 100644 --- a/test/bdd/mock/loginconsent/server.go +++ b/test/bdd/mock/loginconsent/server.go @@ -11,6 +11,7 @@ import ( "net/http" "time" + "github.com/google/uuid" "github.com/gorilla/mux" "github.com/hyperledger/aries-framework-go/spi/storage" "github.com/ory/hydra-client-go/client" @@ -21,6 +22,8 @@ import ( const ( loginChallengeCookieName = "bdd_test_cookie_login_challenge" consentChallengeCookieName = "bdd_test_cookie_consent_challenge" + skipConsentCookieName = "skipConsent" + skipConsentTrue = "true" ) func newServer(c *config) *server { @@ -45,6 +48,7 @@ func newServer(c *config) *server { mockRouter := s.router.PathPrefix("/mock").Subrouter() mockRouter.HandleFunc("/login", s.loginHandler).Methods(http.MethodGet) + mockRouter.HandleFunc("/login", s.postLoginHandler).Methods(http.MethodPost) mockRouter.HandleFunc("/authn", s.userAuthNHandler).Methods(http.MethodPost) mockRouter.HandleFunc("/consent", s.consentHandler).Methods(http.MethodGet) mockRouter.HandleFunc("/authz", s.userAuthZHandler).Methods(http.MethodPost) @@ -102,7 +106,7 @@ func (s *server) loginHandler(w http.ResponseWriter, r *http.Request) { Value: challenge, }) - _, err := w.Write([]byte("mock login UI")) + _, err := w.Write([]byte("\n\n
\n\nmock login UI
\n\n\n\n\n\n\n\n")) if err != nil { logger.Errorf("failed to write imaginary UI: %s", err.Error()) @@ -112,19 +116,41 @@ func (s *server) loginHandler(w http.ResponseWriter, r *http.Request) { logger.Infof("rendered mock login UI in response to request %s", r.URL.String()) } +func (s *server) postLoginHandler(w http.ResponseWriter, r *http.Request) { + logger.Infof("success case %s", r.URL.String()) + + http.SetCookie(w, &http.Cookie{ + Name: skipConsentCookieName, + Value: skipConsentTrue, + }) + + s.completeLogin(w, r, &AuthConfigRequest{ + Sub: uuid.New().String(), + }) +} + func (s *server) userAuthNHandler(w http.ResponseWriter, r *http.Request) { - cookie, err := r.Cookie(loginChallengeCookieName) + request := &AuthConfigRequest{} + + err := json.NewDecoder(r.Body).Decode(request) if err != nil { - logger.Errorf("failed to fetch cookie %s: %s", loginChallengeCookieName, err.Error()) + logger.Errorf("failed to decode auth request: %s", err.Error()) return } - request := &AuthConfigRequest{} + http.SetCookie(w, &http.Cookie{ + Name: skipConsentCookieName, + Value: "", + }) - err = json.NewDecoder(r.Body).Decode(request) + s.completeLogin(w, r, request) +} + +func (s *server) completeLogin(w http.ResponseWriter, r *http.Request, request *AuthConfigRequest) { + cookie, err := r.Cookie(loginChallengeCookieName) if err != nil { - logger.Errorf("failed to decode auth request: %s", err.Error()) + logger.Errorf("failed to fetch cookie %s: %s", loginChallengeCookieName, err.Error()) return } @@ -190,6 +216,19 @@ func (s *server) userAuthNHandler(w http.ResponseWriter, r *http.Request) { func (s *server) consentHandler(w http.ResponseWriter, r *http.Request) { logger.Infof("handling request: %s", r.URL.String()) + skipConsent, err := r.Cookie(skipConsentCookieName) + if err != nil { + logger.Errorf("failed to fetch cookie %s: %s", skipConsentCookieName, err.Error()) + + return + } + + logger.Infof("consent skip value %s", skipConsent.Value) + + if skipConsent.Value == skipConsentTrue { + s.completeConsent(w, r, &ConsentConfigRequest{UserClaims: &UserClaims{}}, r.URL.Query().Get("consent_challenge")) + } + challenge := r.URL.Query().Get("consent_challenge") if challenge == "" { logger.Errorf("missing consent_challenge") @@ -202,7 +241,7 @@ func (s *server) consentHandler(w http.ResponseWriter, r *http.Request) { Value: challenge, }) - _, err := w.Write([]byte("mock consent UI")) + _, err = w.Write([]byte("mock consent UI")) if err != nil { logger.Errorf("failed to write imaginary UI: %s", err.Error()) @@ -229,11 +268,15 @@ func (s *server) userAuthZHandler(w http.ResponseWriter, r *http.Request) { return } + s.completeConsent(w, r, request, cookie.Value) +} + +func (s *server) completeConsent(w http.ResponseWriter, r *http.Request, request *ConsentConfigRequest, consentChallenge string) { params := admin.NewGetConsentRequestParams() params.SetContext(r.Context()) params.SetHTTPClient(s.httpClient) - params.SetConsentChallenge(cookie.Value) + params.SetConsentChallenge(consentChallenge) consent, err := s.hydra.GetConsentRequest(params) if err != nil { @@ -262,7 +305,7 @@ func (s *server) userAuthZHandler(w http.ResponseWriter, r *http.Request) { reject := admin.NewRejectConsentRequestParams() reject.SetContext(r.Context()) reject.SetHTTPClient(s.httpClient) - reject.SetConsentChallenge(cookie.Value) + reject.SetConsentChallenge(consentChallenge) rejected, err := s.hydra.RejectConsentRequest(reject) if err != nil { diff --git a/test/bdd/pkg/gnap/steps.go b/test/bdd/pkg/gnap/steps.go index 107ff23..a2b03e9 100644 --- a/test/bdd/pkg/gnap/steps.go +++ b/test/bdd/pkg/gnap/steps.go @@ -11,6 +11,8 @@ import ( "crypto/rand" "fmt" "net/http" + "net/http/cookiejar" + "strings" "github.com/cucumber/godog" "github.com/hyperledger/aries-framework-go/pkg/doc/jose/jwk" @@ -24,6 +26,12 @@ import ( const ( authServerURL = "https://auth.trustbloc.local:8070" expectedInteractURL = authServerURL + "/gnap/interact" + + oidcProviderSelectorURL = authServerURL + "/oauth2/login" + oidcCallbackURLURL = authServerURL + "/oauth2/callback" + authServerSignUpURL = authServerURL + "/ui/sign-up" + + mockOIDCProviderName = "mockbank1" // providers.yaml ) type Steps struct { @@ -31,6 +39,7 @@ type Steps struct { gnapClient *as.Client pubKeyJWK jwk.JWK authResp *gnap.AuthResponse + browser *http.Client } func NewSteps(ctx *bddctx.BDDContext) *Steps { @@ -97,7 +106,7 @@ func (s *Steps) txnRequest() error { if authResp.Interact.Redirect != expectedInteractURL { return fmt.Errorf( - "invalid interact url: expected %s got %s", + "invalid interact url: expected=%s actual=%s", expectedInteractURL, authResp.Interact.Redirect, ) } @@ -108,13 +117,51 @@ func (s *Steps) txnRequest() error { } func (s *Steps) interactRedirect() error { - // TODO get interact url + // initialise the browser + s.initBrowser() + + // redirect to interact url + response, err := s.browser.Get(s.authResp.Interact.Redirect) + if err != nil { + return err + } + + defer func() { + closeErr := response.Body.Close() + if closeErr != nil { + fmt.Printf("WARNING - failed to close http response body: %s\n", closeErr.Error()) + } + }() + + // validate the redirect url + if response.Request.URL.String() != authServerSignUpURL { + return fmt.Errorf( + "invalid ui redirect url: expected=%s actual=%s", authServerSignUpURL, response.Request.URL.String(), + ) + } + + // select provider + request := fmt.Sprintf("%s?provider=%s", oidcProviderSelectorURL, mockOIDCProviderName) - // TODO use browser to redirect + fmt.Println(request) + + result, err := s.browser.Get(fmt.Sprintf("%s?provider=%s", oidcProviderSelectorURL, mockOIDCProviderName)) + if err != nil { + return fmt.Errorf("failed to redirect to OIDC provider url %s: %w", request, err) + } - // TODO select provider + // login to third party oidc + loginResp, err := s.browser.Post(result.Request.URL.String(), "", nil) + if err != nil { + return err + } - // TODO login to third party oidc + if !strings.HasPrefix(loginResp.Request.URL.String(), oidcCallbackURLURL) { + return fmt.Errorf( + "invalid oidc callbackURL prefix expected=%s actual=%s", + oidcCallbackURLURL, loginResp.Request.URL.String(), + ) + } // TODO get the redirect back @@ -130,3 +177,17 @@ func (s *Steps) continueRequest() error { return nil } + +func (s *Steps) initBrowser() error { + jar, err := cookiejar.New(nil) + if err != nil { + return fmt.Errorf("failed to init cookie jar: %w", err) + } + + s.browser = &http.Client{ + Jar: jar, + Transport: &http.Transport{TLSClientConfig: s.ctx.TLSConfig()}, + } + + return nil +} diff --git a/test/bdd/pkg/login/steps.go b/test/bdd/pkg/login/steps.go index 0de0cf7..522234d 100644 --- a/test/bdd/pkg/login/steps.go +++ b/test/bdd/pkg/login/steps.go @@ -22,7 +22,7 @@ import ( ) const ( - AUTH_HOST = "https://localhost:8070" + AUTH_HOST = "https://auth.trustbloc.local:8070" hubAuthHydraAdminURL = "https://localhost:4445" hubAuthOIDCProviderURL = "https://localhost:4444/" hubAuthOIDCProviderSelectionURL = AUTH_HOST + "/ui"