Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework devlocal methods to make signing in from /create easier #1824

Merged
merged 7 commits into from Mar 19, 2019
@@ -1181,7 +1181,7 @@ func main() {
localAuthMux.Handle(pat.Get("/login"), authentication.NewUserListHandler(authContext, dbConnection))
localAuthMux.Handle(pat.Post("/login"), authentication.NewAssignUserHandler(authContext, dbConnection, clientAuthSecretKey, noSessionTimeout))
localAuthMux.Handle(pat.Post("/new"), authentication.NewCreateAndLoginUserHandler(authContext, dbConnection, clientAuthSecretKey, noSessionTimeout, useSecureCookie))
localAuthMux.Handle(pat.Post("/create"), authentication.NewCreateUserHandler(authContext, dbConnection, clientAuthSecretKey, noSessionTimeout))
localAuthMux.Handle(pat.Post("/create"), authentication.NewCreateUserHandler(authContext, dbConnection, clientAuthSecretKey, noSessionTimeout, useSecureCookie))

devlocalCa, err := ioutil.ReadFile(v.GetString("devlocal-ca")) // #nosec
if err != nil {
@@ -21,6 +21,15 @@ import (
"github.com/transcom/mymove/pkg/testingsuite"
)

const (
// TspTestHost
TspTestHost string = "tsp.example.com"
// OfficeTestHost
OfficeTestHost string = "office.example.com"
// MilTestHost
MilTestHost string = "mil.example.com"
)

type AuthSuite struct {
testingsuite.PopTestSuite
logger Logger
@@ -60,16 +69,15 @@ func (suite *AuthSuite) TestAuthorizationLogoutHandler() {

fakeToken := "some_token"
fakeUUID, _ := uuid.FromString("39b28c92-0506-4bef-8b57-e39519f42dc2")
officeTestHost := "office.example.com"
callbackPort := 1234
responsePattern := regexp.MustCompile(`href="(.+)"`)

req := httptest.NewRequest("GET", fmt.Sprintf("http://%s/auth/logout", officeTestHost), nil)
req := httptest.NewRequest("GET", fmt.Sprintf("http://%s/auth/logout", OfficeTestHost), nil)
session := auth.Session{
ApplicationName: auth.OfficeApp,
UserID: fakeUUID,
IDToken: fakeToken,
Hostname: officeTestHost,
Hostname: OfficeTestHost,
}
ctx := auth.SetSessionInRequestContext(req, &session)
req = req.WithContext(ctx)
@@ -93,7 +101,7 @@ func (suite *AuthSuite) TestAuthorizationLogoutHandler() {
postRedirectURI, err := url.Parse(params["post_logout_redirect_uri"][0])

suite.Nil(err)
suite.Equal(officeTestHost, postRedirectURI.Hostname())
suite.Equal(OfficeTestHost, postRedirectURI.Hostname())
suite.Equal(strconv.Itoa(callbackPort), postRedirectURI.Port())
token := params["id_token_hint"][0]
suite.Equal(fakeToken, token, "handler id_token")
@@ -10,6 +10,7 @@ import (
"github.com/gobuffalo/pop"
"github.com/gofrs/uuid"
"github.com/gorilla/csrf"
"github.com/pkg/errors"
"go.uber.org/zap"

"github.com/transcom/mymove/pkg/auth"
@@ -134,32 +135,72 @@ func (h AssignUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
}

loginUser(devlocalAuthHandler(h), user, w, r)
session := loginUser(devlocalAuthHandler(h), user, w, r)
if session == nil {
return
}
http.Redirect(w, r, h.landingURL(session), http.StatusSeeOther)
}

// CreateUserHandler creates a new user
type CreateUserHandler devlocalAuthHandler

// NewCreateUserHandler creates a new CreateUserHandler
func NewCreateUserHandler(ac Context, db *pop.Connection, clientAuthSecretKey string, noSessionTimeout bool) CreateUserHandler {
func NewCreateUserHandler(ac Context, db *pop.Connection, clientAuthSecretKey string, noSessionTimeout bool, useSecureCookie bool) CreateUserHandler {
handler := CreateUserHandler{
Context: ac,
db: db,
clientAuthSecretKey: clientAuthSecretKey,
noSessionTimeout: noSessionTimeout,
useSecureCookie: useSecureCookie,
}
return handler
}

// CreateUserHandler creates a user, primarily used in automated testing
func (h CreateUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user := createUser(devlocalAuthHandler(h), w, r)
if user == nil {
return
}
session := loginUser(devlocalAuthHandler(h), user, w, r)
if session == nil {
return
}
jsonOut, _ := json.Marshal(user)
fmt.Fprintf(w, string(jsonOut))
}

// CreateAndLoginUserHandler creates and then logs in a new user
type CreateAndLoginUserHandler devlocalAuthHandler

// NewCreateAndLoginUserHandler creates a new CreateAndLoginUserHandler
func NewCreateAndLoginUserHandler(ac Context, db *pop.Connection, clientAuthSecretKey string, noSessionTimeout bool, useSecureCookie bool) CreateAndLoginUserHandler {
handler := CreateAndLoginUserHandler{
Context: ac,
db: db,
clientAuthSecretKey: clientAuthSecretKey,
noSessionTimeout: noSessionTimeout,
useSecureCookie: useSecureCookie,
}
return handler
}

// CreateAndLoginUserHandler creates a user and logs them in
func (h CreateAndLoginUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
user := createUser(devlocalAuthHandler(h), w, r)
if user == nil {
return
}
session := loginUser(devlocalAuthHandler(h), user, w, r)
if session == nil {
return
}
http.Redirect(w, r, h.landingURL(session), http.StatusSeeOther)
}

// createUser creates a user
func createUser(h devlocalAuthHandler, w http.ResponseWriter, r *http.Request) models.User {
func createUser(h devlocalAuthHandler, w http.ResponseWriter, r *http.Request) *models.User {
id := uuid.Must(uuid.NewV4())

now := time.Now()
@@ -174,92 +215,102 @@ func createUser(h devlocalAuthHandler, w http.ResponseWriter, r *http.Request) m
if err != nil {
h.logger.Error("could not create user", zap.Error(err))
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
return nil
}
if verrs.Count() != 0 {
h.logger.Error("validation errors creating user", zap.Stringer("errors", verrs))
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
return nil
}
return user
return &user
}

// CreateAndLoginUserHandler creates and then logs in a new user
type CreateAndLoginUserHandler devlocalAuthHandler
// createSession creates a new session for the user
func createSession(h devlocalAuthHandler, user *models.User, w http.ResponseWriter, r *http.Request) (*auth.Session, error) {
session := auth.SessionFromRequestContext(r)
if session == nil {
return nil, errors.New("Unable to create session from request context")
}

// NewCreateAndLoginUserHandler creates a new CreateAndLoginUserHandler
func NewCreateAndLoginUserHandler(ac Context, db *pop.Connection, clientAuthSecretKey string, noSessionTimeout bool, useSecureCookie bool) CreateAndLoginUserHandler {
handler := CreateAndLoginUserHandler{
Context: ac,
db: db,
clientAuthSecretKey: clientAuthSecretKey,
noSessionTimeout: noSessionTimeout,
useSecureCookie: useSecureCookie,
lgUUID := user.LoginGovUUID.String()
userIdentity, err := models.FetchUserIdentity(h.db, lgUUID)

if err != nil {
return nil, errors.Wrapf(err, "Unable to fetch user identity from LoginGovUUID %s", lgUUID)
}
return handler
}

// CreateAndLoginUserHandler creates a user and logs them in
func (h CreateAndLoginUserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Assign user identity to session
session.IDToken = "devlocal"
session.UserID = userIdentity.ID
session.Email = userIdentity.Email
session.Disabled = userIdentity.Disabled

user := createUser(devlocalAuthHandler(h), w, r)
loginUser(devlocalAuthHandler(h), &user, w, r)
}
if userIdentity.ServiceMemberID != nil {
session.ServiceMemberID = *(userIdentity.ServiceMemberID)
}

func loginUser(handler devlocalAuthHandler, user *models.User, w http.ResponseWriter, r *http.Request) {
session := auth.SessionFromRequestContext(r)
if session == nil {
handler.logger.Error("Session missing")
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
return
if userIdentity.OfficeUserID != nil {
session.OfficeUserID = *(userIdentity.OfficeUserID)
}

userIdentity, err := models.FetchUserIdentity(handler.db, user.LoginGovUUID.String())
if err == nil { // Someone we know already
session.IDToken = "devlocal"
session.UserID = userIdentity.ID
session.Email = userIdentity.Email
if userIdentity.TspUserID != nil {
session.TspUserID = *(userIdentity.TspUserID)
}

if userIdentity.Disabled {
handler.logger.Error("Disabled user requesting authentication", zap.String("email", session.Email))
http.Error(w, http.StatusText(403), http.StatusForbidden)
return
}
if userIdentity.DpsUserID != nil {
session.DpsUserID = *(userIdentity.DpsUserID)
}

if userIdentity.ServiceMemberID != nil {
session.ServiceMemberID = *(userIdentity.ServiceMemberID)
}
session.FirstName = userIdentity.FirstName()
session.LastName = userIdentity.LastName()
session.Middle = userIdentity.Middle()

if userIdentity.DpsUserID != nil {
session.DpsUserID = *(userIdentity.DpsUserID)
}
// Writing out the session cookie logs in the user
h.logger.Info("logged in", zap.Any("session", session))
auth.WriteSessionCookie(w, session, h.clientAuthSecretKey, h.noSessionTimeout, h.logger, h.useSecureCookie)

if userIdentity.OfficeUserID != nil {
session.OfficeUserID = *(userIdentity.OfficeUserID)
} else if session.IsOfficeApp() {
handler.logger.Error("Non-office user authenticated at office site", zap.String("email", session.Email))
http.Error(w, http.StatusText(401), http.StatusUnauthorized)
return
}
return session, nil
}

if userIdentity.TspUserID != nil {
session.TspUserID = *(userIdentity.TspUserID)
} else if session.IsTspApp() {
handler.logger.Error("Non-TSP user authenticated at TSP site", zap.String("email", session.Email))
http.Error(w, http.StatusText(401), http.StatusUnauthorized)
return
}
// verifySessionWithApp returns an error if the user id for a specific app is not available
func verifySessionWithApp(session *auth.Session) error {

// TODO: Should this be a check that we do? Or will all office and tsp users also be service members?
// if (session.ServiceMemberID == uuid.UUID{}) && session.IsMilApp() {
// return errors.Errorf("Non-service member user %s authenticated at service member site", session.Email)
// }

if (session.OfficeUserID == uuid.UUID{}) && session.IsOfficeApp() {
return errors.Errorf("Non-office user %s authenticated at office site", session.Email)
}

if (session.TspUserID == uuid.UUID{}) && session.IsTspApp() {
return errors.Errorf("Non-TSP user %s authenticated at TSP site", session.Email)
}

return nil
}

session.FirstName = userIdentity.FirstName()
session.LastName = userIdentity.LastName()
session.Middle = userIdentity.Middle()
} else {
handler.logger.Error("Error loading Identity.", zap.Error(err))
// loginUser creates a session for the user and verifies the session against the app
func loginUser(h devlocalAuthHandler, user *models.User, w http.ResponseWriter, r *http.Request) *auth.Session {
session, err := createSession(devlocalAuthHandler(h), user, w, r)
if err != nil {
h.logger.Error("Could not create session", zap.Error(err))
http.Error(w, http.StatusText(500), http.StatusInternalServerError)
return
return nil
}

handler.logger.Info("logged in", zap.Any("session", session))
auth.WriteSessionCookie(w, session, handler.clientAuthSecretKey, handler.noSessionTimeout, handler.logger, handler.useSecureCookie)
if session.Disabled {
h.logger.Info("Disabled user requesting authentication", zap.Error(err), zap.String("email", session.Email))
http.Error(w, http.StatusText(403), http.StatusForbidden)
return nil
}

This comment has been minimized.

Copy link
@chrisgilmerproj

chrisgilmerproj Mar 18, 2019

Author Contributor

This is because of the merge. Instead of throwing a 403 directly when fetching the userIdentity I have to throw the Disabled value into the session and then check it afterwards. It's not my favorite method but it keeps the concerns separated.


lURL := handler.landingURL(session)
http.Redirect(w, r, lURL, http.StatusSeeOther)
err = verifySessionWithApp(session)
if err != nil {
h.logger.Error("User unauthorized", zap.Error(err))
http.Error(w, http.StatusText(401), http.StatusUnauthorized)
return nil
}
return session
}
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.