From d42c896b939fdaa2cbfd8c83f4a0584e17505978 Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Thu, 27 Jul 2023 17:56:31 +0000 Subject: [PATCH 1/9] Constructed Okta login auth redirect after accepting eula terms. This is the product of pair programming and is being shared for team development purposes. There is still much to be done and cleaned up on. --- .gitignore | 1 + cmd/milmove/serve.go | 7 +- pkg/handlers/authentication/auth.go | 16 +++-- pkg/handlers/authentication/okta.go | 79 ++++++++++++++++++++++ pkg/handlers/routing/base_routing_suite.go | 5 +- 5 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 pkg/handlers/authentication/okta.go diff --git a/.gitignore b/.gitignore index d7d0bd13e66..a7bbf7cbd3e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # direnv .envrc.local .envrc.chamber +.envrc.okta # dependencies /vendor diff --git a/cmd/milmove/serve.go b/cmd/milmove/serve.go index 5f134e09bd8..21b84ea01ab 100644 --- a/cmd/milmove/serve.go +++ b/cmd/milmove/serve.go @@ -484,14 +484,15 @@ func buildRoutingConfig(appCtx appcontext.AppContext, v *viper.Viper, redisPool appCtx.Logger().Fatal("Must provide the Login.gov hostname parameter, exiting") } - // Register Login.gov authentication provider for My.(move.mil) - loginGovProvider, err := authentication.InitAuth(v, appCtx.Logger(), + // Register Okta authentication provider for My.(move.mil) + oktaProvider, err := authentication.InitAuth(v, appCtx.Logger(), appNames) if err != nil { appCtx.Logger().Fatal("Registering login provider", zap.Error(err)) } - routingConfig.AuthContext = authentication.NewAuthContext(appCtx.Logger(), loginGovProvider, loginGovCallbackProtocol, loginGovCallbackPort) + // TODO: Update loginGov callbacks to Okta + routingConfig.AuthContext = authentication.NewAuthContext(appCtx.Logger(), *oktaProvider, loginGovCallbackProtocol, loginGovCallbackPort) // Email notificationSender, err := notifications.InitEmail(v, awsSession, appCtx.Logger()) diff --git a/pkg/handlers/authentication/auth.go b/pkg/handlers/authentication/auth.go index 89dc3464bfc..3e324b9e485 100644 --- a/pkg/handlers/authentication/auth.go +++ b/pkg/handlers/authentication/auth.go @@ -457,8 +457,10 @@ func (context *Context) GetFeatureFlag(flag string) bool { } // Context is the common handler type for auth handlers +// TODO: Remove loginGov type Context struct { loginGovProvider LoginGovProvider + oktaProvider OktaProvider callbackTemplate string featureFlags map[string]bool } @@ -470,9 +472,9 @@ type FeatureFlag struct { } // NewAuthContext creates an Context -func NewAuthContext(_ *zap.Logger, loginGovProvider LoginGovProvider, callbackProtocol string, callbackPort int) Context { +func NewAuthContext(_ *zap.Logger, oktaProvider OktaProvider, callbackProtocol string, callbackPort int) Context { context := Context{ - loginGovProvider: loginGovProvider, + oktaProvider: oktaProvider, callbackTemplate: fmt.Sprintf("%s://%%s:%d/", callbackProtocol, callbackPort), } return context @@ -584,6 +586,7 @@ func StateCookieName(session *auth.Session) string { } // RedirectHandler constructs the Login.gov authentication URL and redirects to it +// TODO: More login.gov to Okta func (h RedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { appCtx := h.AppContextFromRequest(r) @@ -593,7 +596,7 @@ func (h RedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - loginData, err := h.loginGovProvider.AuthorizationURL(r) + loginData, err := h.oktaProvider.AuthorizationURL(r) if err != nil { http.Error(w, http.StatusText(500), http.StatusInternalServerError) return @@ -1139,14 +1142,15 @@ func fetchToken(code string, clientID string, loginGovProvider LoginGovProvider) return &session, err } -// InitAuth initializes the Login.gov provider -func InitAuth(v *viper.Viper, logger *zap.Logger, appnames auth.ApplicationServername) (LoginGovProvider, error) { +// InitAuth initializes the Okta provider +func InitAuth(v *viper.Viper, logger *zap.Logger, appnames auth.ApplicationServername) (*OktaProvider, error) { loginGovCallbackProtocol := v.GetString(cli.LoginGovCallbackProtocolFlag) loginGovCallbackPort := v.GetInt(cli.LoginGovCallbackPortFlag) loginGovSecretKey := v.GetString(cli.LoginGovSecretKeyFlag) loginGovHostname := v.GetString(cli.LoginGovHostnameFlag) loginGovProvider := NewLoginGovProvider(loginGovHostname, loginGovSecretKey, logger) + oktaProvider := NewOktaProvider(logger) err := loginGovProvider.RegisterProvider( appnames.MilServername, v.GetString(cli.LoginGovMyClientIDFlag), @@ -1156,5 +1160,5 @@ func InitAuth(v *viper.Viper, logger *zap.Logger, appnames auth.ApplicationServe v.GetString(cli.LoginGovAdminClientIDFlag), loginGovCallbackProtocol, loginGovCallbackPort) - return loginGovProvider, err + return oktaProvider, err } diff --git a/pkg/handlers/authentication/okta.go b/pkg/handlers/authentication/okta.go new file mode 100644 index 00000000000..299d892d8c8 --- /dev/null +++ b/pkg/handlers/authentication/okta.go @@ -0,0 +1,79 @@ +package authentication + +import ( + "net/http" + "net/url" + "os" + + "github.com/markbates/goth/providers/okta" + "github.com/transcom/mymove/pkg/auth" + "go.uber.org/zap" +) + +type OktaProvider struct { + okta.Provider + Logger *zap.Logger +} + +type OktaData struct { + RedirectURL string + Nonce string +} + +func (op *OktaProvider) AuthorizationURL(r *http.Request) (*OktaData, error) { + // if os.Getenv("OKTA_OAUTH2_ISSUER") == "" { + // err := errors.New("Issuer not set") + // op.logger.Error("Issuer not set", zap.Error(err)) + // return nil, err + // } + + state := generateNonce() + + sess, err := op.BeginAuth(state) + if err != nil { + op.Logger.Error("Goth begin auth", zap.Error(err)) + return nil, err + } + + baseURL, err := sess.GetAuthURL() + if err != nil { + op.Logger.Error("Goth get auth URL", zap.Error(err)) + return nil, err + } + + authURL, err := url.Parse(baseURL) + if err != nil { + op.Logger.Error("Parse auth URL", zap.Error(err)) + return nil, err + } + + // TODO: Verify CAC authenticator + params := authURL.Query() + session := auth.SessionFromRequestContext(r) + // TODO: Switch away from idmanagement - This is login.gov + if session.IsAdminApp() { + // This specifies that a user has been authenticated with an HSPD12 credential, via their CAC. Both acr_values must be specified. + params.Add("acr_values", "http://idmanagement.gov/ns/assurance/ial/1 http://idmanagement.gov/ns/assurance/aal/3?hspd12=true") + } else { + params.Add("acr_values", "http://idmanagement.gov/ns/assurance/loa/1") + } + params.Add("nonce", state) + params.Set("scope", "openid email") + + authURL.RawQuery = params.Encode() + + return &OktaData{authURL.String(), state}, nil +} + +func NewOktaProvider(logger *zap.Logger) *OktaProvider { + return &OktaProvider{ + Provider: *okta.New( + os.Getenv("OKTA_OAUTH2_CLIENT_ID"), + os.Getenv("OKTA_OAUTH2_CLIENT_SECRET"), + os.Getenv("OKTA_OAUTH2_ISSUER"), + "http://milmovelocal:3000/", + "openid", "profile", "email", + ), + Logger: logger, + } +} diff --git a/pkg/handlers/routing/base_routing_suite.go b/pkg/handlers/routing/base_routing_suite.go index 5fcdbae9194..1a103de564b 100644 --- a/pkg/handlers/routing/base_routing_suite.go +++ b/pkg/handlers/routing/base_routing_suite.go @@ -60,9 +60,8 @@ func (suite *BaseRoutingSuite) RoutingConfig() *Config { fakeS3 := storageTest.NewFakeS3Storage(true) handlerConfig.SetFileStorer(fakeS3) - fakeLoginGovProvider := authentication.NewLoginGovProvider("fakeHostname", "secret_key", suite.Logger()) - - authContext := authentication.NewAuthContext(suite.Logger(), fakeLoginGovProvider, "http", 80) + fakeOktaProvider := authentication.NewOktaProvider(suite.Logger()) + authContext := authentication.NewAuthContext(suite.Logger(), *fakeOktaProvider, "http", 80) fakeFs := afero.NewMemMapFs() fakeBase := "fakebase" From aca65d20aa274ba7688419a3fb59d5fc0ba42263 Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Wed, 2 Aug 2023 19:23:25 +0000 Subject: [PATCH 2/9] Began registration and key management of three additional auth providers for Okta --- pkg/cli/auth.go | 48 +++++++++++++++++++++++++++++ pkg/handlers/authentication/auth.go | 30 +++++++++--------- pkg/handlers/authentication/okta.go | 44 ++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 14 deletions(-) diff --git a/pkg/cli/auth.go b/pkg/cli/auth.go index 14ff3f894ee..f02a54c6363 100644 --- a/pkg/cli/auth.go +++ b/pkg/cli/auth.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "os" "regexp" "strings" @@ -21,6 +22,10 @@ const ( //RA Validator: jneuner@mitre.org //RA Modified Severity: CAT III // #nosec G101 + // + // Additional notes for Okta flags - these variable names with "secret" are to identify the name of the flag as well + // These variables do not store the secret + // ClientAuthSecretKeyFlag is the Client Auth Secret Key Flag ClientAuthSecretKeyFlag string = "client-auth-secret-key" // LoginGovCallbackProtocolFlag is the Login.gov Callback Protocol Flag @@ -37,6 +42,25 @@ const ( LoginGovAdminClientIDFlag string = "login-gov-admin-client-id" // LoginGovHostnameFlag is the Login.gov Hostname Flag LoginGovHostnameFlag string = "login-gov-hostname" + + // Okta tenant flags + OktaTenantIssuerURLFlag string = "okta-tenant-issuer-url" + OktaTenantCallbackPortFlag string = "okta-tenant-callback-port" + // Okta Customer client id and secret flags + OktaCustomerSecretKeyFlag string = "okta-customer-secret-key" + OktaCustomerClientIDFlag string = "okta-customr-client-id" + OktaCustomerHostnameFlag string = "okta-customer-hostname" + OktaCustomerCallbackProtocolFlag string = "okta-customer-callback-protocol" + // Okta Office client id and secret flags + OktaOfficeSecretKeyFlag string = "okta-office-secret-key" + OktaOfficeClientIDFlag string = "okta-office-client-id" + OktaOfficeHostnameFlag string = "okta-office-hostname" + OktaOfficeCallbackProtocolFlag string = "okta-office-callback-protocol" + // Okta Admin client id and secret flags + OktaAdminSecretKeyFlag string = "okta-admin-secret-key" + OktaAdminClientIDFlag string = "okta-admin-client-id" + OktaAdminHostnameFlag string = "okta-admin-hostname" + OktaAdminCallbackProtocolFlag string = "okta-admin-callback-protocol" ) type errInvalidClientID struct { @@ -58,6 +82,30 @@ func InitAuthFlags(flag *pflag.FlagSet) { flag.String(LoginGovOfficeClientIDFlag, "", "Client ID registered with login gov.") flag.String(LoginGovAdminClientIDFlag, "", "Client ID registered with login gov.") flag.String(LoginGovHostnameFlag, "secure.login.gov", "Hostname for communicating with login gov.") + + // TODO: Replace Okta os.Getenv + + // Okta flags + flag.String(OktaTenantIssuerURLFlag, os.Getenv("OKTA_TENANT_ISSUER_URL"), "Okta tenant issuer URL.") + flag.Int(OktaTenantCallbackPortFlag, 443, "Okta tenant callback port.") + + // Customer flags + flag.String(OktaCustomerSecretKeyFlag, os.Getenv("OKTA_CUSTOMER_SECRET_KEY"), "Okta customer secret key.") + flag.String(OktaCustomerClientIDFlag, os.Getenv("OKTA_CUSTOMER_CLIENT_ID"), "Okta customer client ID.") + flag.String(OktaCustomerHostnameFlag, os.Getenv("OKTA_CUSTOMER_HOSTNAME"), "Okta customer hostname.") + flag.String(OktaCustomerCallbackProtocolFlag, os.Getenv("OKTA_CUSTOMER_CALLBACK_PROTOCOL"), "Okta customer callback protocol.") + + // Office flags + flag.String(OktaOfficeSecretKeyFlag, os.Getenv("OKTA_OFFICE_SECRET_KEY"), "Okta office secret key.") + flag.String(OktaOfficeClientIDFlag, os.Getenv("OKTA_OFFICE_CLIENT_ID"), "Okta office client ID.") + flag.String(OktaOfficeHostnameFlag, os.Getenv("OKTA_OFFICE_HOSTNAME"), "Okta office hostname.") + flag.String(OktaOfficeCallbackProtocolFlag, os.Getenv("OKTA_OFFICE_CALLBACK_PROTOCOL"), "Okta office callback protocol.") + + // Admin flags + flag.String(OktaAdminSecretKeyFlag, os.Getenv("OKTA_ADMIN_SECRET_KEY"), "Okta admin secret key.") + flag.String(OktaAdminClientIDFlag, os.Getenv("OKTA_ADMIN_CLIENT_ID"), "Okta admin client ID.") + flag.String(OktaAdminHostnameFlag, os.Getenv("OKTA_ADMIN_HOSTNAME"), "Okta admin hostname.") + flag.String(OktaAdminCallbackProtocolFlag, os.Getenv("OKTA_ADMIN_CALLBACK_PROTOCOL"), "Okta admin callback protocol.") } // CheckAuth validates Auth command line flags diff --git a/pkg/handlers/authentication/auth.go b/pkg/handlers/authentication/auth.go index 3e324b9e485..5d807ec6922 100644 --- a/pkg/handlers/authentication/auth.go +++ b/pkg/handlers/authentication/auth.go @@ -1142,23 +1142,25 @@ func fetchToken(code string, clientID string, loginGovProvider LoginGovProvider) return &session, err } -// InitAuth initializes the Okta provider +// InitAuth initializes the Okta and Logingov provider func InitAuth(v *viper.Viper, logger *zap.Logger, appnames auth.ApplicationServername) (*OktaProvider, error) { - loginGovCallbackProtocol := v.GetString(cli.LoginGovCallbackProtocolFlag) - loginGovCallbackPort := v.GetInt(cli.LoginGovCallbackPortFlag) - loginGovSecretKey := v.GetString(cli.LoginGovSecretKeyFlag) - loginGovHostname := v.GetString(cli.LoginGovHostnameFlag) - loginGovProvider := NewLoginGovProvider(loginGovHostname, loginGovSecretKey, logger) oktaProvider := NewOktaProvider(logger) - err := loginGovProvider.RegisterProvider( - appnames.MilServername, - v.GetString(cli.LoginGovMyClientIDFlag), - appnames.OfficeServername, + err := oktaProvider.RegisterProviders( + v.GetString(cli.OktaCustomerHostnameFlag), + v.GetString(cli.OktaTenantIssuerURLFlag), + v.GetString(cli.OktaCustomerClientIDFlag), + v.GetString(cli.OktaCustomerSecretKeyFlag), + v.GetString(cli.OktaCustomerHostnameFlag), + v.GetString(cli.OktaTenantIssuerURLFlag), + v.GetString(cli.OktaOfficeClientIDFlag), + v.GetString(cli.OktaOfficeSecretKeyFlag), + v.GetString(cli.OktaOfficeHostnameFlag), + v.GetString(cli.OktaTenantIssuerURLFlag), + v.GetString(cli.OktaAdminClientIDFlag), + v.GetString(cli.OktaAdminSecretKeyFlag), v.GetString(cli.LoginGovOfficeClientIDFlag), - appnames.AdminServername, - v.GetString(cli.LoginGovAdminClientIDFlag), - loginGovCallbackProtocol, - loginGovCallbackPort) + v.GetInt(cli.OktaTenantCallbackPortFlag), + v.GetString(cli.OktaTenantIssuerURLFlag)) return oktaProvider, err } diff --git a/pkg/handlers/authentication/okta.go b/pkg/handlers/authentication/okta.go index 299d892d8c8..6023f4cffca 100644 --- a/pkg/handlers/authentication/okta.go +++ b/pkg/handlers/authentication/okta.go @@ -5,11 +5,17 @@ import ( "net/url" "os" + "github.com/markbates/goth" "github.com/markbates/goth/providers/okta" "github.com/transcom/mymove/pkg/auth" "go.uber.org/zap" ) +const customerProviderName = "customerProvider" + +// const officeProviderName = "officeProvider" //used in the login_gov.go +// const adminProviderName = "adminProvider" // used in login_gov.go + type OktaProvider struct { okta.Provider Logger *zap.Logger @@ -77,3 +83,41 @@ func NewOktaProvider(logger *zap.Logger) *OktaProvider { Logger: logger, } } + +// This function allows us to wrap new registered providers with the zap logger. The initial Okta provider is already wrapped +func wrapOktaProvider(provider *okta.Provider, logger *zap.Logger) *OktaProvider { + return &OktaProvider{ + Provider: *provider, + Logger: logger, + } +} + +// This function allows us to register a new Okta provider with Goth. This is primarily used +// for the three different Okta applications we're supporting: Customer, Office, and Admin +func (op *OktaProvider) RegisterProvider(providerName string, clientID string, clientSecret string, issuerURL string, callbackURL string) error { + + oktaProvider := okta.NewCustomisedURL(clientID, clientSecret, callbackURL, issuerURL+"/v1/authorize", issuerURL+"/v1/token", issuerURL, issuerURL+"/v1/userinfo", "openid", "profile", "email") + + // set provider name for the Okta provider + oktaProvider.SetName(providerName) + + goth.UseProviders( + wrapOktaProvider(oktaProvider, op.Logger), + ) + + return nil +} + +func (op *OktaProvider) RegisterProviders(customerHostname string, customerCallbackUrl string, customerClientID string, customerSecret string, officeHostname string, officeCallbackUrl string, officeClientID string, officeSecret string, adminHostname string, adminCallbackUrl string, adminClientID string, adminSecret string, callbackProtocol string, callbackPort int, oktaIssuer string) error { + customerProvider := okta.New(customerClientID, customerSecret, oktaIssuer, customerCallbackUrl, "openid", "profile", "email") + officeProvider := okta.New(officeClientID, officeSecret, oktaIssuer, officeCallbackUrl, "openid", "profile", "email") + adminProvider := okta.New(adminClientID, adminSecret, oktaIssuer, adminCallbackUrl, "openid", "profile", "email") + + goth.UseProviders( + wrapOktaProvider(customerProvider, op.Logger), + wrapOktaProvider(officeProvider, op.Logger), + wrapOktaProvider(adminProvider, op.Logger), + ) + + return nil +} From 4f80af9a60e0187d2199538ce241934225e99a3a Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Fri, 4 Aug 2023 12:27:54 +0000 Subject: [PATCH 3/9] Completed initial functionality of okta providers for all three subdomains. Login redirect is working as intended. Modified gitignore for better vscode debug support --- .gitignore | 1 + pkg/handlers/authentication/auth.go | 54 +++++++++++++++++------- pkg/handlers/authentication/okta.go | 65 +++++++++++++++-------------- 3 files changed, 72 insertions(+), 48 deletions(-) diff --git a/.gitignore b/.gitignore index a7bbf7cbd3e..47495393f7a 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ bin/ #debugging /cmd/milmove/debug /cmd/webserver/debug +__debug_bin* # misc .DS_Store diff --git a/pkg/handlers/authentication/auth.go b/pkg/handlers/authentication/auth.go index 5d807ec6922..fc4c003dce7 100644 --- a/pkg/handlers/authentication/auth.go +++ b/pkg/handlers/authentication/auth.go @@ -9,6 +9,7 @@ import ( "io" "net/http" "net/url" + "os" "strings" "time" @@ -22,7 +23,6 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/auth" - "github.com/transcom/mymove/pkg/cli" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/logging" "github.com/transcom/mymove/pkg/models" @@ -1145,22 +1145,44 @@ func fetchToken(code string, clientID string, loginGovProvider LoginGovProvider) // InitAuth initializes the Okta and Logingov provider func InitAuth(v *viper.Viper, logger *zap.Logger, appnames auth.ApplicationServername) (*OktaProvider, error) { + // Create a new Okta Provider. This will be used in the creation of the additional providers for each subdomain oktaProvider := NewOktaProvider(logger) + /* + err := oktaProvider.RegisterProviders( + v.GetString(cli.OktaCustomerHostnameFlag), + v.GetString(cli.OktaTenantIssuerURLFlag), + v.GetString(cli.OktaCustomerClientIDFlag), + v.GetString(cli.OktaCustomerSecretKeyFlag), + v.GetString(cli.OktaCustomerHostnameFlag), + v.GetString(cli.OktaTenantIssuerURLFlag), + v.GetString(cli.OktaOfficeClientIDFlag), + v.GetString(cli.OktaOfficeSecretKeyFlag), + v.GetString(cli.OktaOfficeHostnameFlag), + v.GetString(cli.OktaTenantIssuerURLFlag), + v.GetString(cli.OktaAdminClientIDFlag), + v.GetString(cli.OktaAdminSecretKeyFlag), + v.GetString(cli.LoginGovOfficeClientIDFlag), + v.GetInt(cli.OktaTenantCallbackPortFlag), + v.GetString(cli.OktaTenantIssuerURLFlag)) + */ + + // TODO: Repace temporary envrc values once we get chamber access err := oktaProvider.RegisterProviders( - v.GetString(cli.OktaCustomerHostnameFlag), - v.GetString(cli.OktaTenantIssuerURLFlag), - v.GetString(cli.OktaCustomerClientIDFlag), - v.GetString(cli.OktaCustomerSecretKeyFlag), - v.GetString(cli.OktaCustomerHostnameFlag), - v.GetString(cli.OktaTenantIssuerURLFlag), - v.GetString(cli.OktaOfficeClientIDFlag), - v.GetString(cli.OktaOfficeSecretKeyFlag), - v.GetString(cli.OktaOfficeHostnameFlag), - v.GetString(cli.OktaTenantIssuerURLFlag), - v.GetString(cli.OktaAdminClientIDFlag), - v.GetString(cli.OktaAdminSecretKeyFlag), - v.GetString(cli.LoginGovOfficeClientIDFlag), - v.GetInt(cli.OktaTenantCallbackPortFlag), - v.GetString(cli.OktaTenantIssuerURLFlag)) + os.Getenv("OKTA_CUSTOMER_HOSTNAME"), + "http://milmovelocal:3000/", + os.Getenv("OKTA_CUSTOMER_CLIENT_ID"), + os.Getenv("OKTA_CUSTOMER_SECRET_KEY"), + os.Getenv("OKTA_OFFICE_HOSTNAME"), + "http://officelocal:3000/", + os.Getenv("OKTA_OFFICE_CLIENT_ID"), + os.Getenv("OKTA_OFFICE_SECRET_KEY"), + os.Getenv("OKTA_ADMIN_HOSTNAME"), + "http://adminlocal:3000/", + os.Getenv("OKTA_ADMIN_CLIENT_ID"), + os.Getenv("OKTA_ADMIN_SECRET_KEY"), + "https", + 443, + os.Getenv("OKTA_TENANT_ISSUER_URL"), + ) return oktaProvider, err } diff --git a/pkg/handlers/authentication/okta.go b/pkg/handlers/authentication/okta.go index 6023f4cffca..ffba49d9591 100644 --- a/pkg/handlers/authentication/okta.go +++ b/pkg/handlers/authentication/okta.go @@ -26,16 +26,37 @@ type OktaData struct { Nonce string } +// This function will select the correct provider to use based on its set name. +func getOktaProviderForRequest(r *http.Request, oktaProvider OktaProvider) (goth.Provider, error) { + session := auth.SessionFromRequestContext(r) + providerName := customerProviderName + if session.IsOfficeApp() { + providerName = officeProviderName + } else if session.IsAdminApp() { + providerName = adminProviderName + } + gothProvider, err := goth.GetProvider(providerName) + if err != nil { + return nil, err + } + + return gothProvider, nil +} + +// This function will use the OktaProvider to return the correct authorization URL to use func (op *OktaProvider) AuthorizationURL(r *http.Request) (*OktaData, error) { - // if os.Getenv("OKTA_OAUTH2_ISSUER") == "" { - // err := errors.New("Issuer not set") - // op.logger.Error("Issuer not set", zap.Error(err)) - // return nil, err - // } + + // Retrieve the correct Okta Provider to use to get the correct authorization URL. This will choose from customer, + // office, or admin domains. + provider, err := getOktaProviderForRequest(r, *op) + if err != nil { + op.Logger.Error("Get Goth provider", zap.Error(err)) + return nil, err + } state := generateNonce() - sess, err := op.BeginAuth(state) + sess, err := provider.BeginAuth(state) if err != nil { op.Logger.Error("Goth begin auth", zap.Error(err)) return nil, err @@ -53,16 +74,7 @@ func (op *OktaProvider) AuthorizationURL(r *http.Request) (*OktaData, error) { return nil, err } - // TODO: Verify CAC authenticator params := authURL.Query() - session := auth.SessionFromRequestContext(r) - // TODO: Switch away from idmanagement - This is login.gov - if session.IsAdminApp() { - // This specifies that a user has been authenticated with an HSPD12 credential, via their CAC. Both acr_values must be specified. - params.Add("acr_values", "http://idmanagement.gov/ns/assurance/ial/1 http://idmanagement.gov/ns/assurance/aal/3?hspd12=true") - } else { - params.Add("acr_values", "http://idmanagement.gov/ns/assurance/loa/1") - } params.Add("nonce", state) params.Set("scope", "openid email") @@ -71,6 +83,7 @@ func (op *OktaProvider) AuthorizationURL(r *http.Request) (*OktaData, error) { return &OktaData{authURL.String(), state}, nil } +// TODO: Clean up when working on callback func NewOktaProvider(logger *zap.Logger) *OktaProvider { return &OktaProvider{ Provider: *okta.New( @@ -92,27 +105,15 @@ func wrapOktaProvider(provider *okta.Provider, logger *zap.Logger) *OktaProvider } } -// This function allows us to register a new Okta provider with Goth. This is primarily used -// for the three different Okta applications we're supporting: Customer, Office, and Admin -func (op *OktaProvider) RegisterProvider(providerName string, clientID string, clientSecret string, issuerURL string, callbackURL string) error { - - oktaProvider := okta.NewCustomisedURL(clientID, clientSecret, callbackURL, issuerURL+"/v1/authorize", issuerURL+"/v1/token", issuerURL, issuerURL+"/v1/userinfo", "openid", "profile", "email") - - // set provider name for the Okta provider - oktaProvider.SetName(providerName) - - goth.UseProviders( - wrapOktaProvider(oktaProvider, op.Logger), - ) - - return nil -} - +// Function to register all three providers at once. +// TODO: Split this function up func (op *OktaProvider) RegisterProviders(customerHostname string, customerCallbackUrl string, customerClientID string, customerSecret string, officeHostname string, officeCallbackUrl string, officeClientID string, officeSecret string, adminHostname string, adminCallbackUrl string, adminClientID string, adminSecret string, callbackProtocol string, callbackPort int, oktaIssuer string) error { customerProvider := okta.New(customerClientID, customerSecret, oktaIssuer, customerCallbackUrl, "openid", "profile", "email") officeProvider := okta.New(officeClientID, officeSecret, oktaIssuer, officeCallbackUrl, "openid", "profile", "email") adminProvider := okta.New(adminClientID, adminSecret, oktaIssuer, adminCallbackUrl, "openid", "profile", "email") - + customerProvider.SetName(customerProviderName) + officeProvider.SetName(officeProviderName) + adminProvider.SetName(adminProviderName) goth.UseProviders( wrapOktaProvider(customerProvider, op.Logger), wrapOktaProvider(officeProvider, op.Logger), From 5a13d186d53c176edc7900eebdb24871410711cd Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Fri, 4 Aug 2023 13:24:23 +0000 Subject: [PATCH 4/9] Refactored okta provider code --- pkg/handlers/authentication/auth.go | 48 ++++------------------ pkg/handlers/authentication/okta.go | 64 +++++++++++++++++++---------- 2 files changed, 51 insertions(+), 61 deletions(-) diff --git a/pkg/handlers/authentication/auth.go b/pkg/handlers/authentication/auth.go index fc4c003dce7..d7c82c5888c 100644 --- a/pkg/handlers/authentication/auth.go +++ b/pkg/handlers/authentication/auth.go @@ -9,7 +9,6 @@ import ( "io" "net/http" "net/url" - "os" "strings" "time" @@ -1142,47 +1141,16 @@ func fetchToken(code string, clientID string, loginGovProvider LoginGovProvider) return &session, err } -// InitAuth initializes the Okta and Logingov provider +// InitAuth initializes the Okta provider func InitAuth(v *viper.Viper, logger *zap.Logger, appnames auth.ApplicationServername) (*OktaProvider, error) { // Create a new Okta Provider. This will be used in the creation of the additional providers for each subdomain oktaProvider := NewOktaProvider(logger) - /* - err := oktaProvider.RegisterProviders( - v.GetString(cli.OktaCustomerHostnameFlag), - v.GetString(cli.OktaTenantIssuerURLFlag), - v.GetString(cli.OktaCustomerClientIDFlag), - v.GetString(cli.OktaCustomerSecretKeyFlag), - v.GetString(cli.OktaCustomerHostnameFlag), - v.GetString(cli.OktaTenantIssuerURLFlag), - v.GetString(cli.OktaOfficeClientIDFlag), - v.GetString(cli.OktaOfficeSecretKeyFlag), - v.GetString(cli.OktaOfficeHostnameFlag), - v.GetString(cli.OktaTenantIssuerURLFlag), - v.GetString(cli.OktaAdminClientIDFlag), - v.GetString(cli.OktaAdminSecretKeyFlag), - v.GetString(cli.LoginGovOfficeClientIDFlag), - v.GetInt(cli.OktaTenantCallbackPortFlag), - v.GetString(cli.OktaTenantIssuerURLFlag)) - */ - - // TODO: Repace temporary envrc values once we get chamber access - err := oktaProvider.RegisterProviders( - os.Getenv("OKTA_CUSTOMER_HOSTNAME"), - "http://milmovelocal:3000/", - os.Getenv("OKTA_CUSTOMER_CLIENT_ID"), - os.Getenv("OKTA_CUSTOMER_SECRET_KEY"), - os.Getenv("OKTA_OFFICE_HOSTNAME"), - "http://officelocal:3000/", - os.Getenv("OKTA_OFFICE_CLIENT_ID"), - os.Getenv("OKTA_OFFICE_SECRET_KEY"), - os.Getenv("OKTA_ADMIN_HOSTNAME"), - "http://adminlocal:3000/", - os.Getenv("OKTA_ADMIN_CLIENT_ID"), - os.Getenv("OKTA_ADMIN_SECRET_KEY"), - "https", - 443, - os.Getenv("OKTA_TENANT_ISSUER_URL"), - ) - return oktaProvider, err + err := oktaProvider.RegisterProviders() + if err != nil { + logger.Error("Initializing auth", zap.Error(err)) + return nil, err + } + + return oktaProvider, nil } diff --git a/pkg/handlers/authentication/okta.go b/pkg/handlers/authentication/okta.go index ffba49d9591..1f8859099b2 100644 --- a/pkg/handlers/authentication/okta.go +++ b/pkg/handlers/authentication/okta.go @@ -83,16 +83,8 @@ func (op *OktaProvider) AuthorizationURL(r *http.Request) (*OktaData, error) { return &OktaData{authURL.String(), state}, nil } -// TODO: Clean up when working on callback func NewOktaProvider(logger *zap.Logger) *OktaProvider { return &OktaProvider{ - Provider: *okta.New( - os.Getenv("OKTA_OAUTH2_CLIENT_ID"), - os.Getenv("OKTA_OAUTH2_CLIENT_SECRET"), - os.Getenv("OKTA_OAUTH2_ISSUER"), - "http://milmovelocal:3000/", - "openid", "profile", "email", - ), Logger: logger, } } @@ -106,19 +98,49 @@ func wrapOktaProvider(provider *okta.Provider, logger *zap.Logger) *OktaProvider } // Function to register all three providers at once. -// TODO: Split this function up -func (op *OktaProvider) RegisterProviders(customerHostname string, customerCallbackUrl string, customerClientID string, customerSecret string, officeHostname string, officeCallbackUrl string, officeClientID string, officeSecret string, adminHostname string, adminCallbackUrl string, adminClientID string, adminSecret string, callbackProtocol string, callbackPort int, oktaIssuer string) error { - customerProvider := okta.New(customerClientID, customerSecret, oktaIssuer, customerCallbackUrl, "openid", "profile", "email") - officeProvider := okta.New(officeClientID, officeSecret, oktaIssuer, officeCallbackUrl, "openid", "profile", "email") - adminProvider := okta.New(adminClientID, adminSecret, oktaIssuer, adminCallbackUrl, "openid", "profile", "email") - customerProvider.SetName(customerProviderName) - officeProvider.SetName(officeProviderName) - adminProvider.SetName(adminProviderName) - goth.UseProviders( - wrapOktaProvider(customerProvider, op.Logger), - wrapOktaProvider(officeProvider, op.Logger), - wrapOktaProvider(adminProvider, op.Logger), - ) +// TODO: Use viper instead of os environment variables +func (op *OktaProvider) RegisterProviders() error { + // Declare OIDC scopes to be used within the providers + scope := []string{"openid", "email"} + // Register customer provider + err := op.RegisterOktaProvider(customerProviderName, os.Getenv("OKTA_CUSTOMER_HOSTNAME"), os.Getenv("OKTA_CUSTOMER_CALLBACK_URL"), os.Getenv("OKTA_CUSTOMER_CLIENT_ID"), os.Getenv("OKTA_CUSTOMER_SECRET_KEY"), scope) + if err != nil { + return err + } + // Register office provider + err = op.RegisterOktaProvider(officeProviderName, os.Getenv("OKTA_OFFICE_HOSTNAME"), os.Getenv("OKTA_OFFICE_CALLBACK_URL"), os.Getenv("OKTA_OFFICE_CLIENT_ID"), os.Getenv("OKTA_OFFICE_SECRET_KEY"), scope) + if err != nil { + return err + } + // Register admin provider + err = op.RegisterOktaProvider(adminProviderName, os.Getenv("OKTA_ADMIN_HOSTNAME"), os.Getenv("OKTA_ADMIN_CALLBACK_URL"), os.Getenv("OKTA_ADMIN_CLIENT_ID"), os.Getenv("OKTA_ADMIN_SECRET_KEY"), scope) + if err != nil { + return err + } + + return nil +} +// Create a new Okta provider and register it under the Goth providers +func (op *OktaProvider) RegisterOktaProvider(name string, hostname string, callbackUrl string, clientID string, secret string, scope []string) error { + provider := okta.New(clientID, secret, hostname, callbackUrl, scope...) + provider.SetName(name) + goth.UseProviders(wrapOktaProvider(provider, op.Logger)) + + // Check that the provider exists now + err := verifyProvider(name) + if err != nil { + op.Logger.Error("Could not verify goth provider", zap.Error(err)) + return err + } + return nil +} + +// Check if the provided provider name exists +func verifyProvider(name string) error { + _, err := goth.GetProvider(name) + if err != nil { + return err + } return nil } From 6688fdd80b5bd7b3af10b4d976b89325b9b5dfe5 Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Fri, 4 Aug 2023 13:27:40 +0000 Subject: [PATCH 5/9] Added better error handling under okta register providers --- pkg/handlers/authentication/okta.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/handlers/authentication/okta.go b/pkg/handlers/authentication/okta.go index 1f8859099b2..b93101e3e3f 100644 --- a/pkg/handlers/authentication/okta.go +++ b/pkg/handlers/authentication/okta.go @@ -105,16 +105,19 @@ func (op *OktaProvider) RegisterProviders() error { // Register customer provider err := op.RegisterOktaProvider(customerProviderName, os.Getenv("OKTA_CUSTOMER_HOSTNAME"), os.Getenv("OKTA_CUSTOMER_CALLBACK_URL"), os.Getenv("OKTA_CUSTOMER_CLIENT_ID"), os.Getenv("OKTA_CUSTOMER_SECRET_KEY"), scope) if err != nil { + op.Logger.Error("Could not register customer okta provider", zap.Error(err)) return err } // Register office provider err = op.RegisterOktaProvider(officeProviderName, os.Getenv("OKTA_OFFICE_HOSTNAME"), os.Getenv("OKTA_OFFICE_CALLBACK_URL"), os.Getenv("OKTA_OFFICE_CLIENT_ID"), os.Getenv("OKTA_OFFICE_SECRET_KEY"), scope) if err != nil { + op.Logger.Error("Could not register office okta provider", zap.Error(err)) return err } // Register admin provider err = op.RegisterOktaProvider(adminProviderName, os.Getenv("OKTA_ADMIN_HOSTNAME"), os.Getenv("OKTA_ADMIN_CALLBACK_URL"), os.Getenv("OKTA_ADMIN_CLIENT_ID"), os.Getenv("OKTA_ADMIN_SECRET_KEY"), scope) if err != nil { + op.Logger.Error("Could not register admin okta provider", zap.Error(err)) return err } From 3345a17d791935ed2fd3c4740fdd6fd01868ee71 Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Fri, 4 Aug 2023 13:49:44 +0000 Subject: [PATCH 6/9] Comment adjusting as well as switched okta provider name from customer to my --- pkg/handlers/authentication/auth.go | 10 +++++----- pkg/handlers/authentication/okta.go | 9 ++------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/pkg/handlers/authentication/auth.go b/pkg/handlers/authentication/auth.go index d7c82c5888c..557eb9dbeb6 100644 --- a/pkg/handlers/authentication/auth.go +++ b/pkg/handlers/authentication/auth.go @@ -687,7 +687,7 @@ func invalidPermissionsResponse(appCtx appcontext.AppContext, handlerConfig hand http.Redirect(w, r, landingURL.String(), http.StatusTemporaryRedirect) } -// AuthorizationCallbackHandler handles the callback from the Login.gov authorization flow +// AuthorizationCallbackHandler handles the callback from the Okta.mil authorization flow func (h CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { appCtx := h.AppContextFromRequest(r) if appCtx.Session() == nil { @@ -710,17 +710,17 @@ func (h CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch err { case "access_denied": // The user has either cancelled or declined to authorize the client - appCtx.Logger().Error("ACCESS_DENIED error from login.gov") + appCtx.Logger().Error("ACCESS_DENIED error from okta.mil") case "invalid_request": - appCtx.Logger().Error("INVALID_REQUEST error from login.gov") + appCtx.Logger().Error("INVALID_REQUEST error from okta.mil") landingQuery.Add("error", "INVALID_REQUEST") default: - appCtx.Logger().Error("unknown error from login.gov") + appCtx.Logger().Error("unknown error from okta.mil") landingQuery.Add("error", "UNKNOWN_ERROR") } landingURL.RawQuery = landingQuery.Encode() http.Redirect(w, r, landingURL.String(), http.StatusTemporaryRedirect) - appCtx.Logger().Info("User redirected from login.gov", zap.String("landingURL", landingURL.String())) + appCtx.Logger().Info("User redirected from okta.mil", zap.String("landingURL", landingURL.String())) return } diff --git a/pkg/handlers/authentication/okta.go b/pkg/handlers/authentication/okta.go index b93101e3e3f..5860f5e3bcf 100644 --- a/pkg/handlers/authentication/okta.go +++ b/pkg/handlers/authentication/okta.go @@ -11,11 +11,6 @@ import ( "go.uber.org/zap" ) -const customerProviderName = "customerProvider" - -// const officeProviderName = "officeProvider" //used in the login_gov.go -// const adminProviderName = "adminProvider" // used in login_gov.go - type OktaProvider struct { okta.Provider Logger *zap.Logger @@ -29,7 +24,7 @@ type OktaData struct { // This function will select the correct provider to use based on its set name. func getOktaProviderForRequest(r *http.Request, oktaProvider OktaProvider) (goth.Provider, error) { session := auth.SessionFromRequestContext(r) - providerName := customerProviderName + providerName := milProviderName if session.IsOfficeApp() { providerName = officeProviderName } else if session.IsAdminApp() { @@ -103,7 +98,7 @@ func (op *OktaProvider) RegisterProviders() error { // Declare OIDC scopes to be used within the providers scope := []string{"openid", "email"} // Register customer provider - err := op.RegisterOktaProvider(customerProviderName, os.Getenv("OKTA_CUSTOMER_HOSTNAME"), os.Getenv("OKTA_CUSTOMER_CALLBACK_URL"), os.Getenv("OKTA_CUSTOMER_CLIENT_ID"), os.Getenv("OKTA_CUSTOMER_SECRET_KEY"), scope) + err := op.RegisterOktaProvider(milProviderName, os.Getenv("OKTA_CUSTOMER_HOSTNAME"), os.Getenv("OKTA_CUSTOMER_CALLBACK_URL"), os.Getenv("OKTA_CUSTOMER_CLIENT_ID"), os.Getenv("OKTA_CUSTOMER_SECRET_KEY"), scope) if err != nil { op.Logger.Error("Could not register customer okta provider", zap.Error(err)) return err From 2c26eb14207a4fff61962139f9bbdc0574140c27 Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Wed, 9 Aug 2023 19:31:29 +0000 Subject: [PATCH 7/9] Removed okta from the authentication package and moved it into its own standalone package. Configured redirecthandler and callback handler to use Okta based secrets and providers to handle authentication for each app. Modified logout handler in preparation for upcoming session changes to reflect Okta implementation. Removed login.gov from handler auth context. Added new dependencies for Okta support. Added support for grant_type auth code flow with Okta auth. Modified sessions to accomodate for new Okta information. Removed most of login.gov support from auth.go, th rest to be pruned out after finalizing sessions. --- go.mod | 13 + go.sum | 20 ++ pkg/auth/session.go | 2 + pkg/handlers/authentication/auth.go | 123 +++++---- pkg/handlers/authentication/okta.go | 144 ---------- pkg/handlers/authentication/okta/provider.go | 258 ++++++++++++++++++ .../authentication/okta_auth_code_flow.go | 133 +++++++++ pkg/handlers/routing/base_routing_suite.go | 3 +- pkg/parser/tac/parse_test.go | 2 +- 9 files changed, 501 insertions(+), 197 deletions(-) delete mode 100644 pkg/handlers/authentication/okta.go create mode 100644 pkg/handlers/authentication/okta/provider.go create mode 100644 pkg/handlers/authentication/okta_auth_code_flow.go diff --git a/go.mod b/go.mod index 602e2846f75..0e2ce3ad917 100644 --- a/go.mod +++ b/go.mod @@ -87,6 +87,18 @@ require ( pault.ag/go/pksigner v1.0.2 ) +require ( + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect + github.com/goccy/go-json v0.9.6 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.0 // indirect + github.com/lestrrat-go/httpcc v1.0.0 // indirect + github.com/lestrrat-go/iter v1.0.1 // indirect + github.com/lestrrat-go/jwx v1.2.21 // indirect + github.com/lestrrat-go/option v1.0.0 // indirect + github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 // indirect +) + require ( atomicgo.dev/cursor v0.1.1 // indirect atomicgo.dev/keyboard v0.2.9 // indirect @@ -176,6 +188,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/okta/okta-jwt-verifier-golang v1.3.1 github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/peterbourgon/diskv/v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 23c812d1b44..1bfa365ca89 100644 --- a/go.sum +++ b/go.sum @@ -129,6 +129,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d h1:1iy2qD6JEhHKKhUOA9IWs7mjco7lnw2qx8FsRI2wirE= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= @@ -288,6 +289,8 @@ github.com/gobuffalo/validate/v3 v3.3.3 h1:o7wkIGSvZBYBd6ChQoLxkz2y1pfmhbI4jNJYh github.com/gobuffalo/validate/v3 v3.3.3/go.mod h1:YC7FsbJ/9hW/VjQdmXPvFqvRis4vrRYFxr69WiNZw6g= github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380 h1:JJq8YZiS07gFIMYZxkbbiMrXIglG3k5JPPtdvckcnfQ= github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI= +github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E= github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -522,12 +525,22 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= +github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4= github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= +github.com/lestrrat-go/codegen v1.0.0/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM= +github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc= github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE= +github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A= github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= +github.com/lestrrat-go/jwx v1.2.18/go.mod h1:bWTBO7IHHVMtNunM8so9MT8wD+euEY1PzGEyCnuI2qM= +github.com/lestrrat-go/jwx v1.2.21 h1:n+yG95UMm5ZFsDdvsZmui+bqat4Cj/di4ys6XbgSlE8= github.com/lestrrat-go/jwx v1.2.21/go.mod h1:9cfxnOH7G1gN75CaJP2hKGcxFEx5sPh1abRIA/ZJVh4= +github.com/lestrrat-go/option v0.0.0-20210103042652-6f1ecfceda35/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/pdebug/v3 v3.0.1/go.mod h1:za+m+Ve24yCxTEhR59N7UlnJomWwCiIqbJRmKeiADU4= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -598,8 +611,12 @@ github.com/namsral/flag v1.7.4-pre/go.mod h1:OXldTctbM6SWH1K899kPZcf65KxJiD7Msce github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/okta/okta-jwt-verifier-golang v1.3.1 h1:V+9W5KD3nG7xN0UYtnzXtkurGcs71bLwzPFuUGNMwdE= +github.com/okta/okta-jwt-verifier-golang v1.3.1/go.mod h1:cHffA777f7Yi4K+yDzUp89sGD5v8sk04Pc3CiT1OMR8= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 h1:pSCLCl6joCFRnjpeojzOpEYs4q7Vditq8fySFG5ap3Y= +github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pdfcpu/pdfcpu v0.2.5 h1:7jBh0EOQgxxpe35XjTtEzjHJzVMHO3ZwUn8EYNEA6Ng= github.com/pdfcpu/pdfcpu v0.2.5/go.mod h1:VLoFmLCCnUkneQe2uTjK1ZgPveTUZKGgIb2OP20+W5c= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= @@ -821,6 +838,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= @@ -1099,12 +1117,14 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20200929161345-d7fc70abf50f/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= diff --git a/pkg/auth/session.go b/pkg/auth/session.go index c2339afc9ad..7169aa6e179 100644 --- a/pkg/auth/session.go +++ b/pkg/auth/session.go @@ -232,6 +232,8 @@ type Session struct { AdminUserRole string Roles roles.Roles Permissions []string + AccessToken string + ClientID string } // SetSessionInRequestContext modifies the request's Context() to add the session data diff --git a/pkg/handlers/authentication/auth.go b/pkg/handlers/authentication/auth.go index 557eb9dbeb6..65c3ffc2a55 100644 --- a/pkg/handlers/authentication/auth.go +++ b/pkg/handlers/authentication/auth.go @@ -9,6 +9,7 @@ import ( "io" "net/http" "net/url" + "os" "strings" "time" @@ -23,6 +24,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/handlers/authentication/okta" "github.com/transcom/mymove/pkg/logging" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" @@ -456,10 +458,8 @@ func (context *Context) GetFeatureFlag(flag string) bool { } // Context is the common handler type for auth handlers -// TODO: Remove loginGov type Context struct { - loginGovProvider LoginGovProvider - oktaProvider OktaProvider + oktaProvider okta.OktaProvider callbackTemplate string featureFlags map[string]bool } @@ -471,7 +471,7 @@ type FeatureFlag struct { } // NewAuthContext creates an Context -func NewAuthContext(_ *zap.Logger, oktaProvider OktaProvider, callbackProtocol string, callbackPort int) Context { +func NewAuthContext(_ *zap.Logger, oktaProvider okta.OktaProvider, callbackProtocol string, callbackPort int) Context { context := Context{ oktaProvider: oktaProvider, callbackTemplate: fmt.Sprintf("%s://%%s:%d/", callbackProtocol, callbackPort), @@ -479,7 +479,7 @@ func NewAuthContext(_ *zap.Logger, oktaProvider OktaProvider, callbackProtocol s return context } -// LogoutHandler handles logging the user out of login.gov +// LogoutHandler handles logging the user out of okta.mil type LogoutHandler struct { Context handlers.HandlerConfig @@ -494,6 +494,7 @@ func NewLogoutHandler(ac Context, hc handlers.HandlerConfig) LogoutHandler { return logoutHandler } +// !Needs to be finalized after sessions. func (h LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { appCtx := h.AppContextFromRequest(r) if appCtx.Session() != nil { @@ -507,18 +508,13 @@ func (h LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if appCtx.Session().IDToken != "" { var logoutURL string // All users logged in via devlocal-auth will have this IDToken. We - // don't want to make a call to login.gov for a logout URL as it will + // don't want to make a call to okta.mil for a logout URL as it will // fail for devlocal-auth'ed users. if appCtx.Session().IDToken == "devlocal" { logoutURL = redirectURL } else { - provider, err := getLoginGovProviderForRequest(r, h.loginGovProvider) - if err != nil { - appCtx.Logger().Error("Failed to get provider from request", zap.Error(err)) - http.Error(w, http.StatusText(500), http.StatusInternalServerError) - return - } - logoutURL = h.loginGovProvider.LogoutURL(redirectURL, provider.ClientKey()) + //TODO: Error handling + logoutURL, _ = h.oktaProvider.LogoutURL(appCtx.Session().Hostname, redirectURL, appCtx.Session().ClientID) } if !appCtx.Session().UserID.IsNil() { err := resetUserCurrentSessionID(appCtx) @@ -534,7 +530,7 @@ func (h LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { appCtx.Logger().Info("user logged out") fmt.Fprint(w, logoutURL) } else { - // Can't log out of login.gov without a token, redirect and let them re-auth + // Can't log out of okta.mil without a token, redirect and let them re-auth appCtx.Logger().Info("session exists but has an empty IDToken") if appCtx.Session().UserID != uuid.Nil { @@ -555,9 +551,9 @@ func (h LogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } -// loginStateCookieName is the name given to the cookie storing the encrypted Login.gov state nonce. -const loginStateCookieName = "lg_state" -const loginStateCookieTTLInSecs = 1800 // 30 mins to transit through login.gov. +// loginStateCookieName is the name given to the cookie storing the encrypted okta.mil state nonce. +const loginStateCookieName = "okta_state" +const loginStateCookieTTLInSecs = 1800 // 30 mins to transit through okta.mil. // RedirectHandler handles redirection type RedirectHandler struct { @@ -579,13 +575,13 @@ func shaAsString(nonce string) string { return hex.EncodeToString(s[:]) } -// StateCookieName returns the login.gov state cookie name +// StateCookieName returns the okta.mil state cookie name func StateCookieName(session *auth.Session) string { return fmt.Sprintf("%s_%s", string(session.ApplicationName), loginStateCookieName) } -// RedirectHandler constructs the Login.gov authentication URL and redirects to it -// TODO: More login.gov to Okta +// RedirectHandler constructs the okta.mil authentication URL and redirects to it +// This will be called when logging in func (h RedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { appCtx := h.AppContextFromRequest(r) @@ -601,8 +597,8 @@ func (h RedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // Hash the state/Nonce value sent to login.gov and set the result as an HttpOnly cookie - // Check this when we return from login.gov + // Hash the state/Nonce value sent to okta.mil and set the result as an HttpOnly cookie + // Check this when we return from okta.mil if appCtx.Session() == nil { appCtx.Logger().Error("Session is nil, so cannot get hostname for state Cookie") http.Error(w, http.StatusText(500), http.StatusInternalServerError) @@ -626,7 +622,7 @@ func (h RedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { appCtx.Logger().Info("User has been redirected", zap.Any("redirectURL", loginData.RedirectURL)) } -// CallbackHandler processes a callback from login.gov +// CallbackHandler processes a callback from okta.mil type CallbackHandler struct { Context handlers.HandlerConfig @@ -646,7 +642,7 @@ func NewCallbackHandler(ac Context, hc handlers.HandlerConfig, sender notificati // invalidPermissionsResponse generates an http response when invalid // permissions are encountered. It *also* saves the session // information. This is needed so we have the necessary info to create -// a redirect to logout of login.gov +// a redirect to logout of okta.mil func invalidPermissionsResponse(appCtx appcontext.AppContext, handlerConfig handlers.HandlerConfig, authContext Context, w http.ResponseWriter, r *http.Request) { sessionManager := handlerConfig.SessionManagers().SessionManagerForApplication(appCtx.Session().ApplicationName) @@ -678,7 +674,7 @@ func invalidPermissionsResponse(appCtx appcontext.AppContext, handlerConfig hand } // We need to redirect here because we got to this handler after a - // redirect from login.gov. Our client application did not make + // redirect from okta.mil. Our client application did not make // this request, so we need to redirect to the client app so that // we can present a "pretty" error page to the user appCtx.Logger().Info("Redirect invalid permissions", @@ -731,12 +727,12 @@ func (h CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - // Check the state value sent back from login.gov with the value saved in the cookie + // Check the state value sent back from okta.mil with the value saved in the cookie returnedState := r.URL.Query().Get("state") stateCookieName := StateCookieName(appCtx.Session()) stateCookie, err := r.Cookie(stateCookieName) if err != nil { - appCtx.Logger().Error("Getting login.gov state cookie", + appCtx.Logger().Error("Getting okta.mil state cookie", zap.String("stateCookieName", stateCookieName), zap.String("sessionUserId", appCtx.Session().UserID.String()), zap.Error(err)) @@ -744,26 +740,26 @@ func (h CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { landingQuery.Add("error", "STATE_COOKIE_MISSING") landingURL.RawQuery = landingQuery.Encode() http.Redirect(w, r, landingURL.String(), http.StatusTemporaryRedirect) - appCtx.Logger().Info("User redirected from login.gov", zap.String("landingURL", landingURL.String())) + appCtx.Logger().Info("User redirected from okta.mil", zap.String("landingURL", landingURL.String())) return } hash := stateCookie.Value // case where user has 2 tabs open with different cookies if hash != shaAsString(returnedState) { - appCtx.Logger().Error("State returned from Login.gov does not match state value stored in cookie", + appCtx.Logger().Error("State returned from okta.mil does not match state value stored in cookie", zap.String("state", returnedState), zap.String("cookie", hash), zap.String("hash", shaAsString(returnedState))) - // Delete lg_state cookie + // Delete okta_state cookie auth.DeleteCookie(w, StateCookieName(appCtx.Session())) - appCtx.Logger().Info("lg_state cookie deleted") + appCtx.Logger().Info("okta_state cookie deleted") // This operation will delete all cookies from the session err = sessionManager.Destroy(r.Context()) if err != nil { - appCtx.Logger().Error("Deleting login.gov state cookie", zap.Error(err)) + appCtx.Logger().Error("Deleting okta.mil state cookie", zap.Error(err)) http.Error(w, http.StatusText(500), http.StatusInternalServerError) return } @@ -777,26 +773,49 @@ func (h CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - provider, err := getLoginGovProviderForRequest(r, h.loginGovProvider) - if err != nil { - appCtx.Logger().Error("Get Goth provider", zap.Error(err)) - http.Error(w, http.StatusText(500), http.StatusInternalServerError) + // Exchange code received from login for access token. This is used during the grant_type auth flow + exchange := exchangeCode(r.URL.Query().Get("code"), r, appCtx, hash) + if exchange.Error != "" { + fmt.Println(exchange.Error) + fmt.Println(exchange.ErrorDescription) return } + // Gather Okta org url + orgURL := os.Getenv("OKTA_CUSTOMER_HOSTNAME") + if appCtx.Session().IsOfficeApp() { + orgURL = os.Getenv("OKTA_OFFICE_HOSTNAME") + } else if appCtx.Session().IsAdminApp() { + orgURL = os.Getenv("OKTA_ADMIN_HOSTNAME") + } - openIDUser, idToken, err := provider.FetchUserAndIDTokenByCode(r.URL.Query().Get("code")) - if err != nil { - appCtx.Logger().Error("Login.gov user info request", zap.Error(err)) + // Verify access token + _, verificationError := verifyToken(exchange.IdToken, returnedState, appCtx.Session(), orgURL) + + if verificationError != nil { + appCtx.Logger().Error("token exchange verification", zap.Error(err)) http.Error(w, http.StatusText(500), http.StatusInternalServerError) return } - appCtx.Session().IDToken = idToken - appCtx.Session().Email = openIDUser.Email + // Assign token values to session + appCtx.Session().IDToken = exchange.IdToken + appCtx.Session().AccessToken = exchange.AccessToken + + // Retrieve user info + profileData := getProfileData(r, appCtx, orgURL) + + // ! Continuing with sessions + // TODO: convert profiledata into struct. Previous implementation used goth.User - appCtx.Logger().Info("New Login", zap.String("OID_User", openIDUser.UserID), zap.String("OID_Email", openIDUser.Email), zap.String("Host", appCtx.Session().Hostname)) + appCtx.Session().IDToken = exchange.IdToken + appCtx.Session().Email = profileData["email"] + appCtx.Session().ClientID = profileData["aud"] - result := authorizeUser(r.Context(), appCtx, openIDUser, sessionManager, h.sender) + appCtx.Logger().Info("New Login", zap.String("Okta user", profileData["preferred_username"]), zap.String("Okta email", profileData["email"]), zap.String("Host", appCtx.Session().Hostname)) + // ! Hard coded error auth result. This is because sessions are TODO + // TODO: Implement sessions and remove hard coded auth result error + result := AuthorizationResult(2) + //result := authorizeUser(r.Context(), appCtx, profileData["sub"], sessionManager, h.sender) switch result { case authorizationResultError: http.Error(w, http.StatusText(500), http.StatusInternalServerError) @@ -812,9 +831,9 @@ func authorizeUser(ctx context.Context, appCtx appcontext.AppContext, openIDUser if err == nil { // In this case, we found an existing user associated with the - // unique login.gov UUID (aka OID_User, aka openIDUser.UserID, + // unique okta.mil UUID (aka OID_User, aka openIDUser.UserID, // aka models.User.login_gov_uuid) - appCtx.Logger().Info("Known user: found by login.gov OID_User, checking authorization", zap.String("OID_User", openIDUser.UserID), zap.String("OID_Email", openIDUser.Email), zap.String("user.id", userIdentity.ID.String()), zap.String("user.login_gov_email", userIdentity.Email)) + appCtx.Logger().Info("Known user: found by okta.mil OID_User, checking authorization", zap.String("OID_User", openIDUser.UserID), zap.String("OID_Email", openIDUser.Email), zap.String("user.id", userIdentity.ID.String()), zap.String("user.login_gov_email", userIdentity.Email)) result := AuthorizeKnownUser(ctx, appCtx, userIdentity, sessionManager) appCtx.Logger().Info("Known user authorization", zap.Any("authorizedResult", result), @@ -823,11 +842,11 @@ func authorizeUser(ctx context.Context, appCtx appcontext.AppContext, openIDUser return result } else if err == models.ErrFetchNotFound { // Never heard of them // so far In this case, we can't find an existing user - // associated with the unique login.gov UUID (aka OID_User, + // associated with the unique okta.mil UUID (aka OID_User, // aka openIDUser.UserID, aka models.User.login_gov_uuid). // The authorizeUnknownUser method tries to find a user record // with a matching email address - appCtx.Logger().Info("Unknown user: not found by login.gov OID_User, associating email and checking authorization", zap.String("OID_User", openIDUser.UserID), zap.String("OID_Email", openIDUser.Email)) + appCtx.Logger().Info("Unknown user: not found by okta.mil OID_User, associating email and checking authorization", zap.String("OID_User", openIDUser.UserID), zap.String("OID_Email", openIDUser.Email)) result := authorizeUnknownUser(ctx, appCtx, openIDUser, sessionManager, notificationSender) appCtx.Logger().Info("Unknown user authorization", zap.Any("authorizedResult", result), @@ -1024,7 +1043,7 @@ func authorizeUnknownUser(ctx context.Context, appCtx appcontext.AppContext, ope if err == nil { sysAdminEmail := notifications.GetSysAdminEmail(notificationSender) appCtx.Logger().Info( - "New user account created through Login.gov", + "New user account created through Okta.mil", zap.String("newUserID", user.ID.String()), ) email, emailErr := notifications.NewUserAccountCreated(appCtx, sysAdminEmail, user.ID, user.UpdatedAt) @@ -1057,7 +1076,7 @@ func authorizeUnknownUser(ctx context.Context, appCtx appcontext.AppContext, ope appCtx.Session().ServiceMemberID = newServiceMember.ID } else { // If in Office App or Admin App with valid user - update user's LoginGovUUID - appCtx.Logger().Error("Authorization associating login.gov UUID with user", + appCtx.Logger().Error("Authorization associating okta.mil UUID with user", zap.String("OID_User", openIDUser.UserID), zap.String("OID_Email", openIDUser.Email), zap.String("user.id", user.ID.String()), @@ -1094,6 +1113,8 @@ func authorizeUnknownUser(ctx context.Context, appCtx appcontext.AppContext, ope return authorizationResultAuthorized } +// !This func is currently a leftover from login_gov. +// TODO: Remove once Okta sessions are in place func fetchToken(code string, clientID string, loginGovProvider LoginGovProvider) (*openidConnect.Session, error) { logger := loginGovProvider.logger expiry := auth.GetExpiryTimeFromMinutes(auth.SessionExpiryInMinutes) @@ -1142,10 +1163,10 @@ func fetchToken(code string, clientID string, loginGovProvider LoginGovProvider) } // InitAuth initializes the Okta provider -func InitAuth(v *viper.Viper, logger *zap.Logger, appnames auth.ApplicationServername) (*OktaProvider, error) { +func InitAuth(v *viper.Viper, logger *zap.Logger, appnames auth.ApplicationServername) (*okta.OktaProvider, error) { // Create a new Okta Provider. This will be used in the creation of the additional providers for each subdomain - oktaProvider := NewOktaProvider(logger) + oktaProvider := okta.NewOktaProvider(logger) err := oktaProvider.RegisterProviders() if err != nil { logger.Error("Initializing auth", zap.Error(err)) diff --git a/pkg/handlers/authentication/okta.go b/pkg/handlers/authentication/okta.go deleted file mode 100644 index 5860f5e3bcf..00000000000 --- a/pkg/handlers/authentication/okta.go +++ /dev/null @@ -1,144 +0,0 @@ -package authentication - -import ( - "net/http" - "net/url" - "os" - - "github.com/markbates/goth" - "github.com/markbates/goth/providers/okta" - "github.com/transcom/mymove/pkg/auth" - "go.uber.org/zap" -) - -type OktaProvider struct { - okta.Provider - Logger *zap.Logger -} - -type OktaData struct { - RedirectURL string - Nonce string -} - -// This function will select the correct provider to use based on its set name. -func getOktaProviderForRequest(r *http.Request, oktaProvider OktaProvider) (goth.Provider, error) { - session := auth.SessionFromRequestContext(r) - providerName := milProviderName - if session.IsOfficeApp() { - providerName = officeProviderName - } else if session.IsAdminApp() { - providerName = adminProviderName - } - gothProvider, err := goth.GetProvider(providerName) - if err != nil { - return nil, err - } - - return gothProvider, nil -} - -// This function will use the OktaProvider to return the correct authorization URL to use -func (op *OktaProvider) AuthorizationURL(r *http.Request) (*OktaData, error) { - - // Retrieve the correct Okta Provider to use to get the correct authorization URL. This will choose from customer, - // office, or admin domains. - provider, err := getOktaProviderForRequest(r, *op) - if err != nil { - op.Logger.Error("Get Goth provider", zap.Error(err)) - return nil, err - } - - state := generateNonce() - - sess, err := provider.BeginAuth(state) - if err != nil { - op.Logger.Error("Goth begin auth", zap.Error(err)) - return nil, err - } - - baseURL, err := sess.GetAuthURL() - if err != nil { - op.Logger.Error("Goth get auth URL", zap.Error(err)) - return nil, err - } - - authURL, err := url.Parse(baseURL) - if err != nil { - op.Logger.Error("Parse auth URL", zap.Error(err)) - return nil, err - } - - params := authURL.Query() - params.Add("nonce", state) - params.Set("scope", "openid email") - - authURL.RawQuery = params.Encode() - - return &OktaData{authURL.String(), state}, nil -} - -func NewOktaProvider(logger *zap.Logger) *OktaProvider { - return &OktaProvider{ - Logger: logger, - } -} - -// This function allows us to wrap new registered providers with the zap logger. The initial Okta provider is already wrapped -func wrapOktaProvider(provider *okta.Provider, logger *zap.Logger) *OktaProvider { - return &OktaProvider{ - Provider: *provider, - Logger: logger, - } -} - -// Function to register all three providers at once. -// TODO: Use viper instead of os environment variables -func (op *OktaProvider) RegisterProviders() error { - // Declare OIDC scopes to be used within the providers - scope := []string{"openid", "email"} - // Register customer provider - err := op.RegisterOktaProvider(milProviderName, os.Getenv("OKTA_CUSTOMER_HOSTNAME"), os.Getenv("OKTA_CUSTOMER_CALLBACK_URL"), os.Getenv("OKTA_CUSTOMER_CLIENT_ID"), os.Getenv("OKTA_CUSTOMER_SECRET_KEY"), scope) - if err != nil { - op.Logger.Error("Could not register customer okta provider", zap.Error(err)) - return err - } - // Register office provider - err = op.RegisterOktaProvider(officeProviderName, os.Getenv("OKTA_OFFICE_HOSTNAME"), os.Getenv("OKTA_OFFICE_CALLBACK_URL"), os.Getenv("OKTA_OFFICE_CLIENT_ID"), os.Getenv("OKTA_OFFICE_SECRET_KEY"), scope) - if err != nil { - op.Logger.Error("Could not register office okta provider", zap.Error(err)) - return err - } - // Register admin provider - err = op.RegisterOktaProvider(adminProviderName, os.Getenv("OKTA_ADMIN_HOSTNAME"), os.Getenv("OKTA_ADMIN_CALLBACK_URL"), os.Getenv("OKTA_ADMIN_CLIENT_ID"), os.Getenv("OKTA_ADMIN_SECRET_KEY"), scope) - if err != nil { - op.Logger.Error("Could not register admin okta provider", zap.Error(err)) - return err - } - - return nil -} - -// Create a new Okta provider and register it under the Goth providers -func (op *OktaProvider) RegisterOktaProvider(name string, hostname string, callbackUrl string, clientID string, secret string, scope []string) error { - provider := okta.New(clientID, secret, hostname, callbackUrl, scope...) - provider.SetName(name) - goth.UseProviders(wrapOktaProvider(provider, op.Logger)) - - // Check that the provider exists now - err := verifyProvider(name) - if err != nil { - op.Logger.Error("Could not verify goth provider", zap.Error(err)) - return err - } - return nil -} - -// Check if the provided provider name exists -func verifyProvider(name string) error { - _, err := goth.GetProvider(name) - if err != nil { - return err - } - return nil -} diff --git a/pkg/handlers/authentication/okta/provider.go b/pkg/handlers/authentication/okta/provider.go new file mode 100644 index 00000000000..be21f2801b3 --- /dev/null +++ b/pkg/handlers/authentication/okta/provider.go @@ -0,0 +1,258 @@ +package okta + +import ( + "encoding/base64" + "math/rand" + "net/http" + "net/url" + "os" + + "github.com/markbates/goth" + gothOkta "github.com/markbates/goth/providers/okta" + "go.uber.org/zap" + + "github.com/transcom/mymove/pkg/auth" + "github.com/transcom/mymove/pkg/random" +) + +const MilProviderName = "milProvider" +const OfficeProviderName = "officeProvider" +const AdminProviderName = "adminProvider" + +type OktaProvider struct { + gothOkta.Provider + hostname string + logger *zap.Logger +} + +type OktaData struct { + RedirectURL string + Nonce string + GothSession goth.Session +} + +// This function will select the correct provider to use based on its set name. +func getOktaProviderForRequest(r *http.Request, oktaProvider OktaProvider) (goth.Provider, error) { + session := auth.SessionFromRequestContext(r) + + // Default the provider name to the "MilProviderName" which is the customer application + // It will update based on if office or admin app + providerName := MilProviderName + + // Set the provider name based on of it is an office or admin app. Remember, the provider is slected by its name + if session.IsOfficeApp() { + providerName = OfficeProviderName + } else if session.IsAdminApp() { + providerName = AdminProviderName + } + + // Retrieve the provider based on its name + gothProvider, err := goth.GetProvider(providerName) + if err != nil { + return nil, err + } + + return gothProvider, nil +} + +func getProviderName(r *http.Request) string { + session := auth.SessionFromRequestContext(r) + + // Set the provider name based on of it is an office or admin app. Remember, the provider is slected by its name + if session.IsOfficeApp() { + return OfficeProviderName + } else if session.IsAdminApp() { + return AdminProviderName + } + return MilProviderName +} + +// ! This func will likely come back during continuation of the sessions story +// // This function will return the ClientID of the current provider +// func (op *OktaProvider) ClientID(r *http.Request) (string, error) { +// // Default the provider name to the "MilProviderName" which is the customer application +// providerName := getProviderName(r) + +// // Retrieve the provider based on its name +// gothProvider, err := goth.GetProvider(providerName) +// if err != nil { +// return "", err +// } + +// return gothProvider.ClientKey, nil +// } + +// This function will use the OktaProvider to return the correct authorization URL to use +func (op *OktaProvider) AuthorizationURL(r *http.Request) (*OktaData, error) { + + // Retrieve the correct Okta Provider to use to get the correct authorization URL. This will choose from customer, + // office, or admin domains and use their information to create the URL. + provider, err := getOktaProviderForRequest(r, *op) + if err != nil { + op.logger.Error("Get Goth provider", zap.Error(err)) + return nil, err + } + + // Generate a new state that will later be stored in a cookie for auth + state := generateNonce() + + // Generate a session rom the provider and state (nonce) + sess, err := provider.BeginAuth(state) + if err != nil { + op.logger.Error("Goth begin auth", zap.Error(err)) + return nil, err + } + + // Use the goth.Session to generate the AuthURL. It knows this from the hostname. Currently we are not using a custom auth server + // outside of "default" (Note for Okta, "default" doesn't mean the default server, it just means a server named default) + baseURL, err := sess.GetAuthURL() + if err != nil { + op.logger.Error("Goth get auth URL", zap.Error(err)) + return nil, err + } + + // Parse URL + authURL, err := url.Parse(baseURL) + if err != nil { + op.logger.Error("Parse auth URL", zap.Error(err)) + return nil, err + } + + params := authURL.Query() + // Add the nonce and scope to the URL when getting ready to redirect to the login URL + params.Add("nonce", state) + params.Set("scope", "openid profile email") + + authURL.RawQuery = params.Encode() + + return &OktaData{authURL.String(), state, sess}, nil +} + +func NewOktaProvider(logger *zap.Logger) *OktaProvider { + return &OktaProvider{ + logger: logger, + } +} + +// This function allows us to wrap new registered providers with the zap logger. The initial Okta provider is already wrapped +// This will wrap the gothOkta provider with our own version of OktaProvider (With added methods) +func wrapOktaProvider(provider *gothOkta.Provider, logger *zap.Logger) *OktaProvider { + return &OktaProvider{ + Provider: *provider, + logger: logger, + } +} + +// Function to register all three providers at once. +// TODO: Use viper instead of os environment variables +func (op *OktaProvider) RegisterProviders() error { + + // Declare OIDC scopes to be used within the providers + scope := []string{"openid", "email", "profile"} + + // Register customer provider + err := op.RegisterOktaProvider(MilProviderName, os.Getenv("OKTA_CUSTOMER_HOSTNAME"), os.Getenv("OKTA_CUSTOMER_CALLBACK_URL"), os.Getenv("OKTA_CUSTOMER_CLIENT_ID"), os.Getenv("OKTA_CUSTOMER_SECRET_KEY"), scope) + if err != nil { + op.logger.Error("Could not register customer okta provider", zap.Error(err)) + return err + } + // Register office provider + err = op.RegisterOktaProvider(OfficeProviderName, os.Getenv("OKTA_OFFICE_HOSTNAME"), os.Getenv("OKTA_OFFICE_CALLBACK_URL"), os.Getenv("OKTA_OFFICE_CLIENT_ID"), os.Getenv("OKTA_OFFICE_SECRET_KEY"), scope) + if err != nil { + op.logger.Error("Could not register office okta provider", zap.Error(err)) + return err + } + // Register admin provider + err = op.RegisterOktaProvider(AdminProviderName, os.Getenv("OKTA_ADMIN_HOSTNAME"), os.Getenv("OKTA_ADMIN_CALLBACK_URL"), os.Getenv("OKTA_ADMIN_CLIENT_ID"), os.Getenv("OKTA_ADMIN_SECRET_KEY"), scope) + if err != nil { + op.logger.Error("Could not register admin okta provider", zap.Error(err)) + return err + } + + return nil +} + +// Create a new Okta provider and register it under the Goth providers +func (op *OktaProvider) RegisterOktaProvider(name string, hostname string, callbackUrl string, clientID string, secret string, scope []string) error { + // Use goth to create a new provider + provider := gothOkta.New(clientID, secret, hostname, callbackUrl, scope...) + // Set the name manualy + provider.SetName(name) + // Wrap + wrap := wrapOktaProvider(provider, op.logger) + // Set hostname + wrap.SetHostname(hostname) + // Assign to the active goth providers + goth.UseProviders(wrap) + + // Check that the provider exists now. The previous functions do not have error handling + err := verifyProvider(name) + if err != nil { + op.logger.Error("Could not verify goth provider", zap.Error(err)) + return err + } + return nil +} + +// Check if the provided provider name exists +func verifyProvider(name string) error { + _, err := goth.GetProvider(name) + if err != nil { + return err + } + return nil +} + +func (op OktaProvider) SetHostname(hostname string) { + op.hostname = hostname +} + +func (op OktaProvider) GetHostname() string { + return op.hostname +} + +// TokenURL returns a full URL to retrieve a user token from okta.mil +func (op OktaProvider) TokenURL(r *http.Request) string { + session := auth.SessionFromRequestContext(r) + + tokenURL := session.Hostname + "/oauth2/default/v1/token" + op.logger.Info("Session", zap.String("tokenUrl", tokenURL)) + + return tokenURL +} + +// LogoutURL returns a full URL to log out of login.gov with required params +// !Ensure proper testing after sessions have been handled +// TODO: Ensure works as intended +func (op OktaProvider) LogoutURL(hostname string, redirectURL string, clientId string) (string, error) { + logoutPath, _ := url.Parse(hostname + "/oauth2/v1/logout") + // Parameters taken from https://developers.login.gov/oidc/#logout + params := url.Values{ + "client_id": {clientId}, + "post_logout_redirect_uri": {redirectURL}, + "state": {generateNonce()}, + } + + logoutPath.RawQuery = params.Encode() + strLogoutPath := logoutPath.String() + op.logger.Info("Logout path", zap.String("strLogoutPath", strLogoutPath)) + + return strLogoutPath, nil +} + +func generateNonce() string { + nonceBytes := make([]byte, 64) + //RA Summary: gosec - G404 - Insecure random number source (rand) + //RA: gosec detected use of the insecure package math/rand rather than the more secure cryptographically secure pseudo-random number generator crypto/rand. + //RA: This particular usage is mitigated by sourcing the seed from crypto/rand in order to create the new random number using math/rand. + //RA Developer Status: Mitigated + //RA Validator: jneuner@mitre.org + //RA Validator Status: Mitigated + //RA Modified Severity: CAT III + // #nosec G404 + randomInt := rand.New(random.NewCryptoSeededSource()) + for i := 0; i < 64; i++ { + nonceBytes[i] = byte(randomInt.Int63() % 256) + } + return base64.URLEncoding.EncodeToString(nonceBytes) +} diff --git a/pkg/handlers/authentication/okta_auth_code_flow.go b/pkg/handlers/authentication/okta_auth_code_flow.go new file mode 100644 index 00000000000..4fe294f3e06 --- /dev/null +++ b/pkg/handlers/authentication/okta_auth_code_flow.go @@ -0,0 +1,133 @@ +package authentication + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + + verifier "github.com/okta/okta-jwt-verifier-golang" + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/auth" + "go.uber.org/zap" +) + +// ! See flow here: +// ! https://developer.okta.com/docs/guides/implement-grant-type/authcode/main/ + +func getProfileData(r *http.Request, appCtx appcontext.AppContext, hostname string) map[string]string { + m := make(map[string]string) + + if appCtx.Session().AccessToken == "" { + return m + } + + reqUrl := hostname + "/oauth2/default/v1/userinfo" + + req, _ := http.NewRequest("GET", reqUrl, bytes.NewReader([]byte(""))) + h := req.Header + h.Add("Authorization", "Bearer "+appCtx.Session().AccessToken) + h.Add("Accept", "application/json") + + client := &http.Client{} + resp, _ := client.Do(req) + body, _ := io.ReadAll(resp.Body) + defer resp.Body.Close() + json.Unmarshal(body, &m) + + return m +} + +// ! Refactor after chamber is modified +func verifyToken(t string, nonce string, session *auth.Session, orgURL string) (*verifier.Jwt, error) { + + // Gather Okta information + clientID := os.Getenv("OKTA_CUSTOMER_CLIENT_ID") + if session.IsOfficeApp() { + clientID = os.Getenv("OKTA_OFFICE_CLIENT_ID") + } else if session.IsAdminApp() { + clientID = os.Getenv("OKTA_ADMIN_CLIENT_ID") + } + + tv := map[string]string{} + tv["nonce"] = nonce + tv["aud"] = clientID + + issuer := orgURL + "/oauth2/default" + jv := verifier.JwtVerifier{ + Issuer: issuer, + ClaimsToValidate: tv, + } + + result, err := jv.New().VerifyIdToken(t) + if err != nil { + return nil, fmt.Errorf("%s", err) + } + + if result != nil { + return result, nil + } + + return nil, fmt.Errorf("token could not be verified: %s", "") +} + +// ! Refactor once chamber is holding new secrets +func exchangeCode(code string, r *http.Request, appCtx appcontext.AppContext, hash string) Exchange { + session := auth.SessionFromRequestContext(r) + + appType := "CUSTOMER" + if session.IsOfficeApp() { + appType = "OFFICE" + } else if session.IsAdminApp() { + appType = "ADMIN" + } + + authHeader := base64.StdEncoding.EncodeToString( + []byte(os.Getenv("OKTA_"+appType+"_CLIENT_ID") + ":" + os.Getenv("OKTA_"+appType+"_SECRET_KEY"))) + + q := r.URL.Query() + q.Add("grant_type", "authorization_code") + q.Set("code", code) + // TODO: Replace os.Getenv + q.Add("redirect_uri", os.Getenv("OKTA_"+appType+"_CALLBACK_URL")) + + // TODO: Replace os.Getenv + url := os.Getenv("OKTA_"+appType+"_HOSTNAME") + "/oauth2/default/v1/token?" + q.Encode() + + req, _ := http.NewRequest("POST", url, bytes.NewReader([]byte(""))) + h := req.Header + h.Add("Authorization", "Basic "+authHeader) + h.Add("Accept", "application/json") + h.Add("Content-Type", "application/x-www-form-urlencoded") + h.Add("Connection", "close") + h.Add("Content-Length", "0") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + appCtx.Logger().Error("Code exchange", zap.Error(err)) + } + fmt.Println("t") + body, err := io.ReadAll(resp.Body) + if err != nil { + appCtx.Logger().Error("Code exchange", zap.Error(err)) + } + defer resp.Body.Close() + var exchange Exchange + json.Unmarshal(body, &exchange) + + return exchange +} + +type Exchange struct { + Error string `json:"error,omitempty"` + ErrorDescription string `json:"error_description,omitempty"` + AccessToken string `json:"access_token,omitempty"` + TokenType string `json:"token_type,omitempty"` + ExpiresIn int `json:"expires_in,omitempty"` + Scope string `json:"scope,omitempty"` + IdToken string `json:"id_token,omitempty"` +} diff --git a/pkg/handlers/routing/base_routing_suite.go b/pkg/handlers/routing/base_routing_suite.go index 1a103de564b..ae3cdd9c315 100644 --- a/pkg/handlers/routing/base_routing_suite.go +++ b/pkg/handlers/routing/base_routing_suite.go @@ -15,6 +15,7 @@ import ( "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/handlers/authentication" + "github.com/transcom/mymove/pkg/handlers/authentication/okta" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/notifications" storageTest "github.com/transcom/mymove/pkg/storage/test" @@ -60,7 +61,7 @@ func (suite *BaseRoutingSuite) RoutingConfig() *Config { fakeS3 := storageTest.NewFakeS3Storage(true) handlerConfig.SetFileStorer(fakeS3) - fakeOktaProvider := authentication.NewOktaProvider(suite.Logger()) + fakeOktaProvider := okta.NewOktaProvider(suite.Logger()) authContext := authentication.NewAuthContext(suite.Logger(), *fakeOktaProvider, "http", 80) fakeFs := afero.NewMemMapFs() diff --git a/pkg/parser/tac/parse_test.go b/pkg/parser/tac/parse_test.go index e9caafca76b..5f079564fe0 100644 --- a/pkg/parser/tac/parse_test.go +++ b/pkg/parser/tac/parse_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/suite" "go.uber.org/zap" - "github.com/stretchr/testify/suite" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/parser/tac" "github.com/transcom/mymove/pkg/testingsuite" From 3b87feb32548d84e94f11c80dc9738aaed5aabd2 Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Thu, 10 Aug 2023 12:29:54 +0000 Subject: [PATCH 8/9] Modified auth tests to satisfy lint requirements. Includes replacing login.gov provider with okta. These tests are not finalized. --- pkg/handlers/authentication/auth_test.go | 7 ++++--- pkg/handlers/authentication/devlocal_test.go | 20 ++++++++++---------- pkg/parser/loa/parse_test.go | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/pkg/handlers/authentication/auth_test.go b/pkg/handlers/authentication/auth_test.go index 3f08fed2a8b..b7649252756 100644 --- a/pkg/handlers/authentication/auth_test.go +++ b/pkg/handlers/authentication/auth_test.go @@ -23,6 +23,7 @@ import ( "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/handlers/authentication/okta" "github.com/transcom/mymove/pkg/handlers/ghcapi" "github.com/transcom/mymove/pkg/handlers/internalapi" "github.com/transcom/mymove/pkg/models" @@ -52,7 +53,7 @@ func (suite *AuthSuite) SetupTest() { // AuthContext returns a testing auth context func (suite *AuthSuite) AuthContext() Context { - return NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), + return NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", suite.callbackPort) } @@ -72,8 +73,8 @@ func TestAuthSuite(t *testing.T) { hs.PopTestSuite.TearDown() } -func fakeLoginGovProvider(logger *zap.Logger) LoginGovProvider { - return NewLoginGovProvider("fakeHostname", "secret_key", logger) +func fakeOktaProvider(logger *zap.Logger) *okta.OktaProvider { + return okta.NewOktaProvider(logger) } func (suite *AuthSuite) SetupSessionContext(ctx context.Context, session *auth.Session, sessionManager auth.SessionManager) context.Context { diff --git a/pkg/handlers/authentication/devlocal_test.go b/pkg/handlers/authentication/devlocal_test.go index 123c462b702..8c38e07490f 100644 --- a/pkg/handlers/authentication/devlocal_test.go +++ b/pkg/handlers/authentication/devlocal_test.go @@ -42,7 +42,7 @@ func (suite *AuthSuite) TestCreateUserHandlerMilMove() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") suite.NoError(req.ParseForm()) - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) handler := NewCreateUserHandler(authContext, handlerConfig) rr := httptest.NewRecorder() @@ -87,7 +87,7 @@ func (suite *AuthSuite) TestCreateUserHandlerOffice() { appnames := handlerConfig.AppNames() callbackPort := 1234 - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) handler := NewCreateUserHandler(authContext, handlerConfig) for _, newOfficeUser := range []struct { @@ -193,7 +193,7 @@ func (suite *AuthSuite) TestCreateUserHandlerAdmin() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") suite.NoError(req.ParseForm()) - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) sessionManagers := handlerConfig.SessionManagers() handler := NewCreateUserHandler(authContext, handlerConfig) @@ -250,7 +250,7 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromMilMoveToMilMove() { } ctx := auth.SetSessionInRequestContext(req, &session) - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) sessionManagers := handlerConfig.SessionManagers() handler := NewCreateAndLoginUserHandler(authContext, handlerConfig) rr := httptest.NewRecorder() @@ -292,7 +292,7 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromMilMoveToOffice() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") suite.NoError(req.ParseForm()) - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) sessionManagers := handlerConfig.SessionManagers() handler := NewCreateAndLoginUserHandler(authContext, handlerConfig) @@ -326,7 +326,7 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromMilMoveToAdmin() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") suite.NoError(req.ParseForm()) - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) sessionManagers := handlerConfig.SessionManagers() handler := NewCreateAndLoginUserHandler(authContext, handlerConfig) @@ -360,7 +360,7 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromOfficeToMilMove() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") suite.NoError(req.ParseForm()) - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) sessionManagers := handlerConfig.SessionManagers() handler := NewCreateAndLoginUserHandler(authContext, handlerConfig) @@ -394,7 +394,7 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromOfficeToAdmin() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") suite.NoError(req.ParseForm()) - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) sessionManagers := handlerConfig.SessionManagers() handler := NewCreateAndLoginUserHandler(authContext, handlerConfig) @@ -426,7 +426,7 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromAdminToMilMove() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") suite.NoError(req.ParseForm()) - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) sessionManagers := handlerConfig.SessionManagers() handler := NewCreateAndLoginUserHandler(authContext, handlerConfig) @@ -460,7 +460,7 @@ func (suite *AuthSuite) TestCreateAndLoginUserHandlerFromAdminToOffice() { req.Header.Set("Content-Type", "application/x-www-form-urlencoded; param=value") suite.NoError(req.ParseForm()) - authContext := NewAuthContext(suite.Logger(), fakeLoginGovProvider(suite.Logger()), "http", callbackPort) + authContext := NewAuthContext(suite.Logger(), *fakeOktaProvider(suite.Logger()), "http", callbackPort) sessionManagers := handlerConfig.SessionManagers() handler := NewCreateAndLoginUserHandler(authContext, handlerConfig) diff --git a/pkg/parser/loa/parse_test.go b/pkg/parser/loa/parse_test.go index dbe1804331c..9a1987a1038 100644 --- a/pkg/parser/loa/parse_test.go +++ b/pkg/parser/loa/parse_test.go @@ -6,9 +6,9 @@ import ( "testing" "time" + "github.com/stretchr/testify/suite" "go.uber.org/zap" - "github.com/stretchr/testify/suite" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/parser/loa" From 907dcfd11805386e872bfc58c865ce9f0f7881e1 Mon Sep 17 00:00:00 2001 From: cameroncaci Date: Thu, 10 Aug 2023 12:42:04 +0000 Subject: [PATCH 9/9] Added error handling and lint satisfaction --- .../authentication/okta_auth_code_flow.go | 25 +++++++++++++------ pkg/parser/loa/parse.go | 5 +++- pkg/parser/tac/parse.go | 5 +++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/pkg/handlers/authentication/okta_auth_code_flow.go b/pkg/handlers/authentication/okta_auth_code_flow.go index 4fe294f3e06..86968f51032 100644 --- a/pkg/handlers/authentication/okta_auth_code_flow.go +++ b/pkg/handlers/authentication/okta_auth_code_flow.go @@ -10,19 +10,20 @@ import ( "os" verifier "github.com/okta/okta-jwt-verifier-golang" + "go.uber.org/zap" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/auth" - "go.uber.org/zap" ) // ! See flow here: // ! https://developer.okta.com/docs/guides/implement-grant-type/authcode/main/ -func getProfileData(r *http.Request, appCtx appcontext.AppContext, hostname string) map[string]string { +func getProfileData(r *http.Request, appCtx appcontext.AppContext, hostname string) (map[string]string, error) { m := make(map[string]string) if appCtx.Session().AccessToken == "" { - return m + return m, nil } reqUrl := hostname + "/oauth2/default/v1/userinfo" @@ -36,9 +37,13 @@ func getProfileData(r *http.Request, appCtx appcontext.AppContext, hostname stri resp, _ := client.Do(req) body, _ := io.ReadAll(resp.Body) defer resp.Body.Close() - json.Unmarshal(body, &m) + err := json.Unmarshal(body, &m) + if err != nil { + appCtx.Logger().Error("get profile data", zap.Error(err)) + return nil, err + } - return m + return m, nil } // ! Refactor after chamber is modified @@ -75,7 +80,7 @@ func verifyToken(t string, nonce string, session *auth.Session, orgURL string) ( } // ! Refactor once chamber is holding new secrets -func exchangeCode(code string, r *http.Request, appCtx appcontext.AppContext, hash string) Exchange { +func exchangeCode(code string, r *http.Request, appCtx appcontext.AppContext, hash string) (Exchange, error) { session := auth.SessionFromRequestContext(r) appType := "CUSTOMER" @@ -117,9 +122,13 @@ func exchangeCode(code string, r *http.Request, appCtx appcontext.AppContext, ha } defer resp.Body.Close() var exchange Exchange - json.Unmarshal(body, &exchange) + err = json.Unmarshal(body, &exchange) + if err != nil { + appCtx.Logger().Error("get profile data", zap.Error(err)) + return Exchange{}, err + } - return exchange + return exchange, nil } type Exchange struct { diff --git a/pkg/parser/loa/parse.go b/pkg/parser/loa/parse.go index a3c2f5d6e8b..3952a7778eb 100644 --- a/pkg/parser/loa/parse.go +++ b/pkg/parser/loa/parse.go @@ -41,7 +41,10 @@ func Parse(file io.Reader) ([]models.LineOfAccounting, error) { // and then proceed with parsing the rest of the file. if scanner.Scan() { columnHeaders = strings.Split(scanner.Text(), "|") - ensureFileStructMatchesColumnNames(columnHeaders) + err := ensureFileStructMatchesColumnNames(columnHeaders) + if err != nil { + return nil, errors.New("file column headers do not match") + } } // Process the lines of the .txt file into modeled codes diff --git a/pkg/parser/tac/parse.go b/pkg/parser/tac/parse.go index 9c9b42af3d0..001c9ed40ad 100644 --- a/pkg/parser/tac/parse.go +++ b/pkg/parser/tac/parse.go @@ -39,7 +39,10 @@ func Parse(file io.Reader) ([]models.TransportationAccountingCode, error) { // and then proceed with parsing the rest of the file. if scanner.Scan() { columnHeaders = strings.Split(scanner.Text(), "|") - ensureFileStructMatchesColumnNames(columnHeaders) + err := ensureFileStructMatchesColumnNames(columnHeaders) + if err != nil { + return nil, errors.New("file column headers do not match") + } } // Process the lines of the .txt file into modeled codes