Просмотр исходного кода

Merge branch 'beta.3.github-login-user' of https://github.com/porter-dev/porter

jusrhee 5 лет назад
Родитель
Сommit
482d65a951

+ 0 - 2
cmd/docker-credential-porter/helper/helper.go

@@ -70,7 +70,6 @@ func (p *PorterHelper) getGCR(serverURL string) (user string, secret string, err
 	if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
 	if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
 		token = cachedEntry.AuthorizationToken
 		token = cachedEntry.AuthorizationToken
 	} else {
 	} else {
-		host := viper.GetString("host")
 		projID := viper.GetUint("project")
 		projID := viper.GetUint("project")
 
 
 		client := cmd.GetAPIClient()
 		client := cmd.GetAPIClient()
@@ -196,7 +195,6 @@ func (p *PorterHelper) getECR(serverURL string) (user string, secret string, err
 	if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
 	if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
 		token = cachedEntry.AuthorizationToken
 		token = cachedEntry.AuthorizationToken
 	} else {
 	} else {
-		host := viper.GetString("host")
 		projID := viper.GetUint("project")
 		projID := viper.GetUint("project")
 
 
 		client := cmd.GetAPIClient()
 		client := cmd.GetAPIClient()

+ 3 - 0
internal/models/user.go

@@ -10,6 +10,9 @@ type User struct {
 
 
 	Email    string `json:"email" gorm:"unique"`
 	Email    string `json:"email" gorm:"unique"`
 	Password string `json:"password"`
 	Password string `json:"password"`
+
+	// The github user id used for login (optional)
+	GithubUserID int64
 }
 }
 
 
 // UserExternal represents the User type that is sent over REST
 // UserExternal represents the User type that is sent over REST

+ 9 - 0
internal/repository/gorm/user.go

@@ -44,6 +44,15 @@ func (repo *UserRepository) ReadUserByEmail(email string) (*models.User, error)
 	return user, nil
 	return user, nil
 }
 }
 
 
+// ReadUserByGithubUserID finds a single user based on their github user id
+func (repo *UserRepository) ReadUserByGithubUserID(id int64) (*models.User, error) {
+	user := &models.User{}
+	if err := repo.db.Where("github_user_id = ?", id).First(&user).Error; err != nil {
+		return nil, err
+	}
+	return user, nil
+}
+
 // UpdateUser modifies an existing User in the database
 // UpdateUser modifies an existing User in the database
 func (repo *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
 func (repo *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
 	if err := repo.db.Save(user).Error; err != nil {
 	if err := repo.db.Save(user).Error; err != nil {

+ 40 - 0
internal/repository/gorm/user_test.go

@@ -0,0 +1,40 @@
+package gorm_test
+
+import (
+	"testing"
+
+	"github.com/go-test/deep"
+	"github.com/porter-dev/porter/internal/models"
+)
+
+func TestReadUserByGithubUserID(t *testing.T) {
+	tester := &tester{
+		dbFileName: "./porter_read_user_github.db",
+	}
+
+	setupTestEnv(tester, t)
+	defer cleanup(tester, t)
+
+	user := &models.User{
+		Email:        "test@test.it",
+		Password:     "fake",
+		GithubUserID: 5,
+	}
+
+	user, err := tester.repo.User.CreateUser(user)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	readUser, err := tester.repo.User.ReadUserByGithubUserID(5)
+
+	if err != nil {
+		t.Fatalf("%v\n", err)
+	}
+
+	if diff := deep.Equal(user, readUser); diff != nil {
+		t.Errorf("users not equal:")
+		t.Error(diff)
+	}
+}

+ 15 - 0
internal/repository/memory/user.go

@@ -71,6 +71,21 @@ func (repo *UserRepository) ReadUserByEmail(email string) (*models.User, error)
 	return nil, gorm.ErrRecordNotFound
 	return nil, gorm.ErrRecordNotFound
 }
 }
 
 
+// ReadUserByGithubUserID finds a single user based on their github id field
+func (repo *UserRepository) ReadUserByGithubUserID(id int64) (*models.User, error) {
+	if !repo.canQuery {
+		return nil, errors.New("Cannot read from database")
+	}
+
+	for _, u := range repo.users {
+		if u.GithubUserID == id && id != 0 {
+			return u, nil
+		}
+	}
+
+	return nil, gorm.ErrRecordNotFound
+}
+
 // UpdateUser modifies an existing User in the database
 // UpdateUser modifies an existing User in the database
 func (repo *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
 func (repo *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
 	if !repo.canQuery {
 	if !repo.canQuery {

+ 1 - 0
internal/repository/user.go

@@ -13,6 +13,7 @@ type UserRepository interface {
 	CheckPassword(id int, pwd string) (bool, error)
 	CheckPassword(id int, pwd string) (bool, error)
 	ReadUser(id uint) (*models.User, error)
 	ReadUser(id uint) (*models.User, error)
 	ReadUserByEmail(email string) (*models.User, error)
 	ReadUserByEmail(email string) (*models.User, error)
+	ReadUserByGithubUserID(id int64) (*models.User, error)
 	UpdateUser(user *models.User) (*models.User, error)
 	UpdateUser(user *models.User) (*models.User, error)
 	DeleteUser(user *models.User) (*models.User, error)
 	DeleteUser(user *models.User) (*models.User, error)
 }
 }

+ 11 - 3
server/api/api.go

@@ -68,8 +68,9 @@ type App struct {
 	DBConf config.DBConf
 	DBConf config.DBConf
 
 
 	// oauth-specific clients
 	// oauth-specific clients
-	GithubConf *oauth2.Config
-	DOConf     *oauth2.Config
+	GithubUserConf    *oauth2.Config
+	GithubProjectConf *oauth2.Config
+	DOConf            *oauth2.Config
 
 
 	db         *gorm.DB
 	db         *gorm.DB
 	validator  *vr.Validate
 	validator  *vr.Validate
@@ -117,7 +118,14 @@ func New(conf *AppConfig) (*App, error) {
 
 
 	// if server config contains OAuth client info, create clients
 	// if server config contains OAuth client info, create clients
 	if sc := conf.ServerConf; sc.GithubClientID != "" && sc.GithubClientSecret != "" {
 	if sc := conf.ServerConf; sc.GithubClientID != "" && sc.GithubClientSecret != "" {
-		app.GithubConf = oauth.NewGithubClient(&oauth.Config{
+		app.GithubUserConf = oauth.NewGithubClient(&oauth.Config{
+			ClientID:     sc.GithubClientID,
+			ClientSecret: sc.GithubClientSecret,
+			Scopes:       []string{"read:user"},
+			BaseURL:      sc.ServerURL,
+		})
+
+		app.GithubProjectConf = oauth.NewGithubClient(&oauth.Config{
 			ClientID:     sc.GithubClientID,
 			ClientID:     sc.GithubClientID,
 			ClientSecret: sc.GithubClientSecret,
 			ClientSecret: sc.GithubClientSecret,
 			Scopes:       []string{"repo", "read:user", "workflow"},
 			Scopes:       []string{"repo", "read:user", "workflow"},

+ 1 - 1
server/api/git_action_handler.go

@@ -113,7 +113,7 @@ func (app *App) HandleCreateGitAction(w http.ResponseWriter, r *http.Request) {
 		GitRepoName:    repoSplit[1],
 		GitRepoName:    repoSplit[1],
 		GitRepoOwner:   repoSplit[0],
 		GitRepoOwner:   repoSplit[0],
 		Repo:           *app.Repo,
 		Repo:           *app.Repo,
-		GithubConf:     app.GithubConf,
+		GithubConf:     app.GithubProjectConf,
 		WebhookToken:   release.WebhookToken,
 		WebhookToken:   release.WebhookToken,
 		ProjectID:      uint(projID),
 		ProjectID:      uint(projID),
 		ReleaseName:    name,
 		ReleaseName:    name,

+ 3 - 3
server/api/git_repo_handler.go

@@ -68,7 +68,7 @@ func (app *App) HandleListRepos(w http.ResponseWriter, r *http.Request) {
 
 
 	res := make([]Repo, 0)
 	res := make([]Repo, 0)
 
 
-	client := github.NewClient(app.GithubConf.Client(oauth2.NoContext, tok))
+	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
 
 
 	// list all repositories for specified user
 	// list all repositories for specified user
 	repos, _, err := client.Repositories.List(context.Background(), "", nil)
 	repos, _, err := client.Repositories.List(context.Background(), "", nil)
@@ -101,7 +101,7 @@ func (app *App) HandleGetBranches(w http.ResponseWriter, r *http.Request) {
 	owner := chi.URLParam(r, "owner")
 	owner := chi.URLParam(r, "owner")
 	name := chi.URLParam(r, "name")
 	name := chi.URLParam(r, "name")
 
 
-	client := github.NewClient(app.GithubConf.Client(oauth2.NoContext, tok))
+	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
 
 
 	// List all branches for a specified repo
 	// List all branches for a specified repo
 	branches, _, err := client.Repositories.ListBranches(context.Background(), owner, name, nil)
 	branches, _, err := client.Repositories.ListBranches(context.Background(), owner, name, nil)
@@ -127,7 +127,7 @@ func (app *App) HandleGetBranchContents(w http.ResponseWriter, r *http.Request)
 		return
 		return
 	}
 	}
 
 
-	client := github.NewClient(app.GithubConf.Client(oauth2.NoContext, tok))
+	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
 
 
 	queryParams, err := url.ParseQuery(r.URL.RawQuery)
 	queryParams, err := url.ParseQuery(r.URL.RawQuery)
 	if err != nil {
 	if err != nil {

+ 65 - 10
server/api/oauth_github_handler.go

@@ -7,6 +7,7 @@ import (
 	"strconv"
 	"strconv"
 
 
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/models"
+	"gorm.io/gorm"
 
 
 	"github.com/go-chi/chi"
 	"github.com/go-chi/chi"
 	"github.com/google/go-github/github"
 	"github.com/google/go-github/github"
@@ -28,7 +29,7 @@ func (app *App) HandleGithubOAuthStartUser(w http.ResponseWriter, r *http.Reques
 	}
 	}
 
 
 	// specify access type offline to get a refresh token
 	// specify access type offline to get a refresh token
-	url := app.GithubConf.AuthCodeURL(state, oauth2.AccessTypeOnline)
+	url := app.GithubUserConf.AuthCodeURL(state, oauth2.AccessTypeOnline)
 
 
 	http.Redirect(w, r, url, 302)
 	http.Redirect(w, r, url, 302)
 }
 }
@@ -47,7 +48,7 @@ func (app *App) HandleGithubOAuthStartProject(w http.ResponseWriter, r *http.Req
 	}
 	}
 
 
 	// specify access type offline to get a refresh token
 	// specify access type offline to get a refresh token
-	url := app.GithubConf.AuthCodeURL(state, oauth2.AccessTypeOffline)
+	url := app.GithubProjectConf.AuthCodeURL(state, oauth2.AccessTypeOffline)
 
 
 	http.Redirect(w, r, url, 302)
 	http.Redirect(w, r, url, 302)
 }
 }
@@ -97,7 +98,7 @@ func (app *App) HandleGithubOAuthCallback(w http.ResponseWriter, r *http.Request
 		return
 		return
 	}
 	}
 
 
-	token, err := app.GithubConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
+	token, err := app.GithubProjectConf.Exchange(oauth2.NoContext, r.URL.Query().Get("code"))
 
 
 	if err != nil {
 	if err != nil {
 		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
 		http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
@@ -109,10 +110,41 @@ func (app *App) HandleGithubOAuthCallback(w http.ResponseWriter, r *http.Request
 		return
 		return
 	}
 	}
 
 
-	userID, _ := session.Values["user_id"].(uint)
-	projID, _ := session.Values["project_id"].(uint)
+	if session.Values["project_id"] != nil && session.Values["project_id"] != "" {
+		userID, _ := session.Values["user_id"].(uint)
+		projID, _ := session.Values["project_id"].(uint)
 
 
-	app.updateProjectFromToken(projID, userID, token)
+		app.updateProjectFromToken(projID, userID, token)
+	} else {
+		// otherwise, create the user if not exists
+		user, err := app.upsertUserFromToken(token)
+
+		if err != nil {
+			http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
+			return
+		}
+
+		// log the user in
+		app.Logger.Info().Msgf("New user created: %d", user.ID)
+		var redirect string
+
+		if valR := session.Values["redirect"]; valR != nil {
+			redirect = session.Values["redirect"].(string)
+		}
+
+		session.Values["authenticated"] = true
+		session.Values["user_id"] = user.ID
+		session.Values["email"] = user.Email
+		session.Values["redirect"] = ""
+		session.Save(r, w)
+
+		w.WriteHeader(http.StatusCreated)
+
+		if err := app.sendUser(w, user.ID, user.Email, redirect); err != nil {
+			app.handleErrorFormDecoding(err, ErrUserDecode, w)
+			return
+		}
+	}
 
 
 	if session.Values["query_params"] != "" {
 	if session.Values["query_params"] != "" {
 		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", session.Values["query_params"]), 302)
 		http.Redirect(w, r, fmt.Sprintf("/dashboard?%s", session.Values["query_params"]), 302)
@@ -130,6 +162,7 @@ func (app *App) populateOAuthSession(w http.ResponseWriter, r *http.Request, sta
 
 
 	// need state parameter to validate when redirected
 	// need state parameter to validate when redirected
 	session.Values["state"] = state
 	session.Values["state"] = state
+	session.Values["query_params"] = r.URL.RawQuery
 
 
 	if isProject {
 	if isProject {
 		// read the project id and add it to the session
 		// read the project id and add it to the session
@@ -140,7 +173,6 @@ func (app *App) populateOAuthSession(w http.ResponseWriter, r *http.Request, sta
 		}
 		}
 
 
 		session.Values["project_id"] = uint(projID)
 		session.Values["project_id"] = uint(projID)
-		session.Values["query_params"] = r.URL.RawQuery
 	}
 	}
 
 
 	if err := session.Save(r, w); err != nil {
 	if err := session.Save(r, w); err != nil {
@@ -150,14 +182,37 @@ func (app *App) populateOAuthSession(w http.ResponseWriter, r *http.Request, sta
 	return nil
 	return nil
 }
 }
 
 
-func (app *App) upsertUserFromToken() error {
-	return fmt.Errorf("UNIMPLEMENTED")
+func (app *App) upsertUserFromToken(tok *oauth2.Token) (*models.User, error) {
+	// determine if the user already exists
+	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
+
+	githubUser, _, err := client.Users.Get(context.Background(), "")
+
+	if err != nil {
+		return nil, err
+	}
+
+	user, err := app.Repo.User.ReadUserByGithubUserID(*githubUser.ID)
+
+	// if the user does not exist, create new user
+	if err != nil && err == gorm.ErrRecordNotFound {
+		user = &models.User{
+			Email:        *githubUser.Email,
+			GithubUserID: *githubUser.ID,
+		}
+
+		user, err = app.Repo.User.CreateUser(user)
+	} else if err != nil {
+		return nil, fmt.Errorf("unexpected error occurred:", err.Error())
+	}
+
+	return user, err
 }
 }
 
 
 // updates a project's repository clients with the token information.
 // updates a project's repository clients with the token information.
 func (app *App) updateProjectFromToken(projectID uint, userID uint, tok *oauth2.Token) error {
 func (app *App) updateProjectFromToken(projectID uint, userID uint, tok *oauth2.Token) error {
 	// get the list of repositories that this token has access to
 	// get the list of repositories that this token has access to
-	client := github.NewClient(app.GithubConf.Client(oauth2.NoContext, tok))
+	client := github.NewClient(app.GithubProjectConf.Client(oauth2.NoContext, tok))
 
 
 	user, _, err := client.Users.Get(context.Background(), "")
 	user, _, err := client.Users.Get(context.Background(), "")
 
 

+ 5 - 0
server/router/router.go

@@ -144,6 +144,11 @@ func New(a *api.App) *chi.Mux {
 				mw.WriteAccess,
 				mw.WriteAccess,
 			),
 			),
 		)
 		)
+		r.Method(
+			"GET",
+			"/oauth/login/github",
+			requestlog.NewHandler(a.HandleGithubOAuthStartUser, l),
+		)
 
 
 		r.Method(
 		r.Method(
 			"GET",
 			"GET",