diff --git a/conf/auth.d/github.conf.example b/conf/auth.d/github.conf.example new file mode 100644 index 000000000..976c1ed06 --- /dev/null +++ b/conf/auth.d/github.conf.example @@ -0,0 +1,10 @@ +# This is an example of GitHub authentication +# +id = 106 +type = github +name = GitHub OAuth 2.0 +is_activated = true + +[config] +api_endpoint = https://api.github.com/ + diff --git a/conf/locale/locale_en-US.ini b/conf/locale/locale_en-US.ini index 5966fdbc0..7b558d75b 100644 --- a/conf/locale/locale_en-US.ini +++ b/conf/locale/locale_en-US.ini @@ -1151,6 +1151,7 @@ auths.delete_auth_desc = This authentication is going to be deleted, do you want auths.still_in_used = This authentication is still used by some users, please delete or convert these users to another login type first. auths.deletion_success = Authentication has been deleted successfully! auths.login_source_exist = Login source '%s' already exists. +auths.github_api_endpoint = API Endpoint config.not_set = (not set) config.server_config = Server Configuration diff --git a/models/login_source.go b/models/login_source.go index 546be132d..dd31adc15 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -24,6 +24,7 @@ import ( "gopkg.in/ini.v1" "github.com/gogs/gogs/models/errors" + "github.com/gogs/gogs/pkg/auth/github" "github.com/gogs/gogs/pkg/auth/ldap" "github.com/gogs/gogs/pkg/auth/pam" "github.com/gogs/gogs/pkg/setting" @@ -39,13 +40,15 @@ const ( LOGIN_SMTP // 3 LOGIN_PAM // 4 LOGIN_DLDAP // 5 + LOGIN_GITHUB // 6 ) var LoginNames = map[LoginType]string{ - LOGIN_LDAP: "LDAP (via BindDN)", - LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind - LOGIN_SMTP: "SMTP", - LOGIN_PAM: "PAM", + LOGIN_LDAP: "LDAP (via BindDN)", + LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind + LOGIN_SMTP: "SMTP", + LOGIN_PAM: "PAM", + LOGIN_GITHUB: "GitHub", } var SecurityProtocolNames = map[ldap.SecurityProtocol]string{ @@ -59,6 +62,7 @@ var ( _ core.Conversion = &LDAPConfig{} _ core.Conversion = &SMTPConfig{} _ core.Conversion = &PAMConfig{} + _ core.Conversion = &GITHUBConfig{} ) type LDAPConfig struct { @@ -106,6 +110,18 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) { return jsoniter.Marshal(cfg) } +type GITHUBConfig struct { + ApiEndpoint string // Github service (e.g. https://github.com/api/v1/) +} + +func (cfg *GITHUBConfig) FromDB(bs []byte) error { + return jsoniter.Unmarshal(bs, &cfg) +} + +func (cfg *GITHUBConfig) ToDB() ([]byte, error) { + return jsoniter.Marshal(cfg) +} + // AuthSourceFile contains information of an authentication source file. type AuthSourceFile struct { abspath string @@ -174,6 +190,8 @@ func (s *LoginSource) BeforeSet(colName string, val xorm.Cell) { s.Cfg = new(SMTPConfig) case LOGIN_PAM: s.Cfg = new(PAMConfig) + case LOGIN_GITHUB: + s.Cfg = new(GITHUBConfig) default: panic("unrecognized login source type: " + com.ToStr(*val)) } @@ -209,6 +227,10 @@ func (s *LoginSource) IsPAM() bool { return s.Type == LOGIN_PAM } +func (s *LoginSource) IsGITHUB() bool { + return s.Type == LOGIN_GITHUB +} + func (s *LoginSource) HasTLS() bool { return ((s.IsLDAP() || s.IsDLDAP()) && s.LDAP().SecurityProtocol > ldap.SECURITY_PROTOCOL_UNENCRYPTED) || @@ -249,6 +271,10 @@ func (s *LoginSource) PAM() *PAMConfig { return s.Cfg.(*PAMConfig) } +func (s *LoginSource) GITHUB() *GITHUBConfig { + return s.Cfg.(*GITHUBConfig) +} + func CreateLoginSource(source *LoginSource) error { has, err := x.Get(&LoginSource{Name: source.Name}) if err != nil { @@ -488,6 +514,9 @@ func LoadAuthSources() { case "pam": loginSource.Type = LOGIN_PAM loginSource.Cfg = &PAMConfig{} + case "github": + loginSource.Type = LOGIN_GITHUB + loginSource.Cfg = &GITHUBConfig{} default: log.Fatal(2, "Failed to load authentication source: unknown type '%s'", authType) } @@ -726,7 +755,33 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon } return user, CreateUser(user) } +func LoginViaGITHUB(user *User, login, password string, sourceID int64, cfg *GITHUBConfig, autoRegister bool) (*User, error) { + login_id, fullname, email, url, location, err := github.GITHUBAuth(cfg.ApiEndpoint, login, password) + if err != nil { + if strings.Contains(err.Error(), "Authentication failure") { + return nil, errors.UserNotExist{0, login} + } + return nil, err + } + if !autoRegister { + return user, nil + } + user = &User{ + LowerName: strings.ToLower(login), + Name: login_id, + FullName: fullname, + Email: email, + Website: url, + Passwd: password, + LoginType: LOGIN_GITHUB, + LoginSource: sourceID, + LoginName: login, + IsActive: true, + Location: location, + } + return user, CreateUser(user) +} func remoteUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) { if !source.IsActived { return nil, errors.LoginSourceNotActivated{source.ID} @@ -739,6 +794,8 @@ func remoteUserLogin(user *User, login, password string, source *LoginSource, au return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister) case LOGIN_PAM: return LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister) + case LOGIN_GITHUB: + return LoginViaGITHUB(user, login, password, source.ID, source.Cfg.(*GITHUBConfig), autoRegister) } return nil, errors.InvalidLoginSourceType{source.Type} diff --git a/models/models.go b/models/models.go index 5cd7e5279..18931628d 100644 --- a/models/models.go +++ b/models/models.go @@ -346,6 +346,8 @@ func ImportDatabase(dirPath string, verbose bool) (err error) { bean.Cfg = new(SMTPConfig) case LOGIN_PAM: bean.Cfg = new(PAMConfig) + case LOGIN_GITHUB: + bean.Cfg = new(GITHUBConfig) default: return fmt.Errorf("unrecognized login source type:: %v", tp) } diff --git a/pkg/auth/github/github.go b/pkg/auth/github/github.go new file mode 100644 index 000000000..2a8d8b488 --- /dev/null +++ b/pkg/auth/github/github.go @@ -0,0 +1,45 @@ +package github + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/google/go-github/github" +) + +func GITHUBAuth(apiEndpoint, userName, passwd string) (string, string, string, string, string, error) { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + tp := github.BasicAuthTransport{ + Username: strings.TrimSpace(userName), + Password: strings.TrimSpace(passwd), + Transport: tr, + } + client, err := github.NewEnterpriseClient(apiEndpoint, apiEndpoint, tp.Client()) + if err != nil { + return "", "", "", "", "", errors.New("Authentication failure: GitHub Api Endpoint can not be reached") + } + ctx := context.Background() + user, _, err := client.Users.Get(ctx, "") + if err != nil || user == nil { + fmt.Println(err) + msg := fmt.Sprintf("Authentication failure! Github Api Endpoint authticated failed! User %s", userName) + return "", "", "", "", "", errors.New(msg) + } + + var website = "" + if user.HTMLURL != nil { + website = strings.ToLower(*user.HTMLURL) + } + var location = "" + if user.Location != nil { + location = strings.ToUpper(*user.Location) + } + + return *user.Login, *user.Name, *user.Email, website, location, nil +} diff --git a/pkg/form/auth.go b/pkg/form/auth.go index b2ee3ae80..65f5eac00 100644 --- a/pkg/form/auth.go +++ b/pkg/form/auth.go @@ -11,7 +11,7 @@ import ( type Authentication struct { ID int64 - Type int `binding:"Range(2,5)"` + Type int `binding:"Range(2,6)"` Name string `binding:"Required;MaxSize(30)"` Host string Port int @@ -41,6 +41,7 @@ type Authentication struct { TLS bool SkipVerify bool PAMServiceName string + GithubApiEndpoint string `binding:Required;Url` } func (f *Authentication) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { diff --git a/public/js/gogs.js b/public/js/gogs.js index eb161d174..c57dca4fa 100644 --- a/public/js/gogs.js +++ b/public/js/gogs.js @@ -860,6 +860,7 @@ function initAdmin() { $('.dldap').hide(); $('.smtp').hide(); $('.pam').hide(); + $('.github').hide(); $('.has-tls').hide(); var authType = $(this).val(); @@ -877,7 +878,10 @@ function initAdmin() { case '5': // LDAP $('.dldap').show(); break; - } + case '6': //GITHUB + $('.github').show(); + break; + } if (authType == '2' || authType == '5') { onSecurityProtocolChange() diff --git a/routes/admin/auths.go b/routes/admin/auths.go index d013c793a..a217174a1 100644 --- a/routes/admin/auths.go +++ b/routes/admin/auths.go @@ -7,15 +7,16 @@ package admin import ( "fmt" + "strings" + "github.com/Unknwon/com" "github.com/go-xorm/core" - log "gopkg.in/clog.v1" - "github.com/gogs/gogs/models" "github.com/gogs/gogs/pkg/auth/ldap" "github.com/gogs/gogs/pkg/context" "github.com/gogs/gogs/pkg/form" "github.com/gogs/gogs/pkg/setting" + log "gopkg.in/clog.v1" ) const ( @@ -51,6 +52,7 @@ var ( {models.LoginNames[models.LOGIN_DLDAP], models.LOGIN_DLDAP}, {models.LoginNames[models.LOGIN_SMTP], models.LOGIN_SMTP}, {models.LoginNames[models.LOGIN_PAM], models.LOGIN_PAM}, + {models.LoginNames[models.LOGIN_GITHUB], models.LOGIN_GITHUB}, } securityProtocols = []dropdownItem{ {models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED], ldap.SECURITY_PROTOCOL_UNENCRYPTED}, @@ -138,6 +140,10 @@ func NewAuthSourcePost(c *context.Context, f form.Authentication) { config = &models.PAMConfig{ ServiceName: f.PAMServiceName, } + case models.LOGIN_GITHUB: + config = &models.GITHUBConfig{ + ApiEndpoint: f.GithubApiEndpoint, + } default: c.Error(400) return @@ -220,6 +226,14 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) { config = &models.PAMConfig{ ServiceName: f.PAMServiceName, } + case models.LOGIN_GITHUB: + var apiEndpoint = f.GithubApiEndpoint + if !strings.HasSuffix(apiEndpoint, "/") { + apiEndpoint += "/" + } + config = &models.GITHUBConfig{ + ApiEndpoint: apiEndpoint, + } default: c.Error(400) return diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl index 1db69e690..85ae3eb03 100644 --- a/templates/admin/auth/edit.tmpl +++ b/templates/admin/auth/edit.tmpl @@ -168,6 +168,14 @@ {{end}} + + {{if .Source.IsGITHUB}} + {{ $cfg:=.Source.GITHUB }} +