Skip to content

Commit 01d9576

Browse files
willemvdbkcsoft
authored andcommitted
Oauth2 consumer (#679)
* initial stuff for oauth2 login, fails on: * login button on the signIn page to start the OAuth2 flow and a callback for each provider Only GitHub is implemented for now * show login button only when the OAuth2 consumer is configured (and activated) * create macaron group for oauth2 urls * prevent net/http in modules (other then oauth2) * use a new data sessions oauth2 folder for storing the oauth2 session data * add missing 2FA when this is enabled on the user * add password option for OAuth2 user , for use with git over http and login to the GUI * add tip for registering a GitHub OAuth application * at startup of Gitea register all configured providers and also on adding/deleting of new providers * custom handling of errors in oauth2 request init + show better tip * add ExternalLoginUser model and migration script to add it to database * link a external account to an existing account (still need to handle wrong login and signup) and remove if user is removed * remove the linked external account from the user his settings * if user is unknown we allow him to register a new account or link it to some existing account * sign up with button on signin page (als change OAuth2Provider structure so we can store basic stuff about providers) * from gorilla/sessions docs: "Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!" (we're using gorilla/sessions for storing oauth2 sessions) * use updated goth lib that now supports getting the OAuth2 user if the AccessToken is still valid instead of re-authenticating (prevent flooding the OAuth2 provider)
1 parent fd941db commit 01d9576

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+7275
-137
lines changed

cmd/web.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"github.com/go-macaron/toolbox"
4242
"github.com/urfave/cli"
4343
macaron "gopkg.in/macaron.v1"
44+
context2 "github.com/gorilla/context"
4445
)
4546

4647
// CmdWeb represents the available web sub-command.
@@ -210,6 +211,13 @@ func runWeb(ctx *cli.Context) error {
210211
m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
211212
m.Get("/reset_password", user.ResetPasswd)
212213
m.Post("/reset_password", user.ResetPasswdPost)
214+
m.Group("/oauth2", func() {
215+
m.Get("/:provider", user.SignInOAuth)
216+
m.Get("/:provider/callback", user.SignInOAuthCallback)
217+
})
218+
m.Get("/link_account", user.LinkAccount)
219+
m.Post("/link_account_signin", bindIgnErr(auth.SignInForm{}), user.LinkAccountPostSignIn)
220+
m.Post("/link_account_signup", bindIgnErr(auth.RegisterForm{}), user.LinkAccountPostRegister)
213221
m.Group("/two_factor", func() {
214222
m.Get("", user.TwoFactor)
215223
m.Post("", bindIgnErr(auth.TwoFactorAuthForm{}), user.TwoFactorPost)
@@ -236,6 +244,7 @@ func runWeb(ctx *cli.Context) error {
236244
Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
237245
m.Post("/applications/delete", user.SettingsDeleteApplication)
238246
m.Route("/delete", "GET,POST", user.SettingsDelete)
247+
m.Combo("/account_link").Get(user.SettingsAccountLinks).Post(user.SettingsDeleteAccountLink)
239248
m.Group("/two_factor", func() {
240249
m.Get("", user.SettingsTwoFactor)
241250
m.Post("/regenerate_scratch", user.SettingsTwoFactorRegenerateScratch)
@@ -671,11 +680,11 @@ func runWeb(ctx *cli.Context) error {
671680
var err error
672681
switch setting.Protocol {
673682
case setting.HTTP:
674-
err = runHTTP(listenAddr, m)
683+
err = runHTTP(listenAddr, context2.ClearHandler(m))
675684
case setting.HTTPS:
676-
err = runHTTPS(listenAddr, setting.CertFile, setting.KeyFile, m)
685+
err = runHTTPS(listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
677686
case setting.FCGI:
678-
err = fcgi.Serve(nil, m)
687+
err = fcgi.Serve(nil, context2.ClearHandler(m))
679688
case setting.UnixSocket:
680689
if err := os.Remove(listenAddr); err != nil && !os.IsNotExist(err) {
681690
log.Fatal(4, "Failed to remove unix socket directory %s: %v", listenAddr, err)
@@ -691,7 +700,7 @@ func runWeb(ctx *cli.Context) error {
691700
if err = os.Chmod(listenAddr, os.FileMode(setting.UnixSocketPermission)); err != nil {
692701
log.Fatal(4, "Failed to set permission of unix socket: %v", err)
693702
}
694-
err = http.Serve(listener, m)
703+
err = http.Serve(listener, context2.ClearHandler(m))
695704
default:
696705
log.Fatal(4, "Invalid protocol: %s", setting.Protocol)
697706
}

models/error.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -847,3 +847,43 @@ func IsErrUploadNotExist(err error) bool {
847847
func (err ErrUploadNotExist) Error() string {
848848
return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
849849
}
850+
851+
// ___________ __ .__ .____ .__ ____ ___
852+
// \_ _____/__ ____/ |_ ___________ ____ _____ | | | | ____ ____ |__| ____ | | \______ ___________
853+
// | __)_\ \/ /\ __\/ __ \_ __ \/ \\__ \ | | | | / _ \ / ___\| |/ \ | | / ___// __ \_ __ \
854+
// | \> < | | \ ___/| | \/ | \/ __ \| |__ | |__( <_> ) /_/ > | | \ | | /\___ \\ ___/| | \/
855+
// /_______ /__/\_ \ |__| \___ >__| |___| (____ /____/ |_______ \____/\___ /|__|___| / |______//____ >\___ >__|
856+
// \/ \/ \/ \/ \/ \/ /_____/ \/ \/ \/
857+
858+
// ErrExternalLoginUserAlreadyExist represents a "ExternalLoginUserAlreadyExist" kind of error.
859+
type ErrExternalLoginUserAlreadyExist struct {
860+
ExternalID string
861+
UserID int64
862+
LoginSourceID int64
863+
}
864+
865+
// IsErrExternalLoginUserAlreadyExist checks if an error is a ExternalLoginUserAlreadyExist.
866+
func IsErrExternalLoginUserAlreadyExist(err error) bool {
867+
_, ok := err.(ErrExternalLoginUserAlreadyExist)
868+
return ok
869+
}
870+
871+
func (err ErrExternalLoginUserAlreadyExist) Error() string {
872+
return fmt.Sprintf("external login user already exists [externalID: %s, userID: %d, loginSourceID: %d]", err.ExternalID, err.UserID, err.LoginSourceID)
873+
}
874+
875+
// ErrExternalLoginUserNotExist represents a "ExternalLoginUserNotExist" kind of error.
876+
type ErrExternalLoginUserNotExist struct {
877+
UserID int64
878+
LoginSourceID int64
879+
}
880+
881+
// IsErrExternalLoginUserNotExist checks if an error is a ExternalLoginUserNotExist.
882+
func IsErrExternalLoginUserNotExist(err error) bool {
883+
_, ok := err.(ErrExternalLoginUserNotExist)
884+
return ok
885+
}
886+
887+
func (err ErrExternalLoginUserNotExist) Error() string {
888+
return fmt.Sprintf("external login user link does not exists [userID: %d, loginSourceID: %d]", err.UserID, err.LoginSourceID)
889+
}

models/external_login_user.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright 2017 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package models
6+
7+
import "github.com/markbates/goth"
8+
9+
// ExternalLoginUser makes the connecting between some existing user and additional external login sources
10+
type ExternalLoginUser struct {
11+
ExternalID string `xorm:"NOT NULL"`
12+
UserID int64 `xorm:"NOT NULL"`
13+
LoginSourceID int64 `xorm:"NOT NULL"`
14+
}
15+
16+
// GetExternalLogin checks if a externalID in loginSourceID scope already exists
17+
func GetExternalLogin(externalLoginUser *ExternalLoginUser) (bool, error) {
18+
return x.Get(externalLoginUser)
19+
}
20+
21+
// ListAccountLinks returns a map with the ExternalLoginUser and its LoginSource
22+
func ListAccountLinks(user *User) ([]*ExternalLoginUser, error) {
23+
externalAccounts := make([]*ExternalLoginUser, 0, 5)
24+
err := x.Where("user_id=?", user.ID).
25+
Desc("login_source_id").
26+
Find(&externalAccounts)
27+
28+
if err != nil {
29+
return nil, err
30+
}
31+
32+
return externalAccounts, nil
33+
}
34+
35+
// LinkAccountToUser link the gothUser to the user
36+
func LinkAccountToUser(user *User, gothUser goth.User) error {
37+
loginSource, err := GetActiveOAuth2LoginSourceByName(gothUser.Provider)
38+
if err != nil {
39+
return err
40+
}
41+
42+
externalLoginUser := &ExternalLoginUser{
43+
ExternalID: gothUser.UserID,
44+
UserID: user.ID,
45+
LoginSourceID: loginSource.ID,
46+
}
47+
has, err := x.Get(externalLoginUser)
48+
if err != nil {
49+
return err
50+
} else if has {
51+
return ErrExternalLoginUserAlreadyExist{gothUser.UserID, user.ID, loginSource.ID}
52+
}
53+
54+
_, err = x.Insert(externalLoginUser)
55+
return err
56+
}
57+
58+
// RemoveAccountLink will remove all external login sources for the given user
59+
func RemoveAccountLink(user *User, loginSourceID int64) (int64, error) {
60+
deleted, err := x.Delete(&ExternalLoginUser{UserID: user.ID, LoginSourceID: loginSourceID})
61+
if err != nil {
62+
return deleted, err
63+
}
64+
if deleted < 1 {
65+
return deleted, ErrExternalLoginUserNotExist{user.ID, loginSourceID}
66+
}
67+
return deleted, err
68+
}
69+
70+
// RemoveAllAccountLinks will remove all external login sources for the given user
71+
func RemoveAllAccountLinks(user *User) error {
72+
_, err := x.Delete(&ExternalLoginUser{UserID: user.ID})
73+
return err
74+
}

0 commit comments

Comments
 (0)