Browse Source

Add new Authentication Source: GitHub, including GitHub Enterprise.

pull/5340/head
Lu, Haixun 7 years ago
parent
commit
0bc0725ce1
  1. 10
      conf/auth.d/github.conf.example
  2. 1
      conf/locale/locale_en-US.ini
  3. 65
      models/login_source.go
  4. 2
      models/models.go
  5. 45
      pkg/auth/github/github.go
  6. 3
      pkg/form/auth.go
  7. 6
      public/js/gogs.js
  8. 18
      routes/admin/auths.go
  9. 8
      templates/admin/auth/edit.tmpl
  10. 7
      templates/admin/auth/new.tmpl

10
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/

1
conf/locale/locale_en-US.ini

@ -1147,6 +1147,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.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.deletion_success = Authentication has been deleted successfully!
auths.login_source_exist = Login source '%s' already exists. auths.login_source_exist = Login source '%s' already exists.
auths.github_api_endpoint = API Endpoint
config.not_set = (not set) config.not_set = (not set)
config.server_config = Server Configuration config.server_config = Server Configuration

65
models/login_source.go

@ -24,6 +24,7 @@ import (
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
"github.com/gogs/gogs/models/errors" "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/ldap"
"github.com/gogs/gogs/pkg/auth/pam" "github.com/gogs/gogs/pkg/auth/pam"
"github.com/gogs/gogs/pkg/setting" "github.com/gogs/gogs/pkg/setting"
@ -39,13 +40,15 @@ const (
LOGIN_SMTP // 3 LOGIN_SMTP // 3
LOGIN_PAM // 4 LOGIN_PAM // 4
LOGIN_DLDAP // 5 LOGIN_DLDAP // 5
LOGIN_GITHUB // 6
) )
var LoginNames = map[LoginType]string{ var LoginNames = map[LoginType]string{
LOGIN_LDAP: "LDAP (via BindDN)", LOGIN_LDAP: "LDAP (via BindDN)",
LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind
LOGIN_SMTP: "SMTP", LOGIN_SMTP: "SMTP",
LOGIN_PAM: "PAM", LOGIN_PAM: "PAM",
LOGIN_GITHUB: "GitHub",
} }
var SecurityProtocolNames = map[ldap.SecurityProtocol]string{ var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
@ -59,6 +62,7 @@ var (
_ core.Conversion = &LDAPConfig{} _ core.Conversion = &LDAPConfig{}
_ core.Conversion = &SMTPConfig{} _ core.Conversion = &SMTPConfig{}
_ core.Conversion = &PAMConfig{} _ core.Conversion = &PAMConfig{}
_ core.Conversion = &GITHUBConfig{}
) )
type LDAPConfig struct { type LDAPConfig struct {
@ -106,6 +110,18 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) {
return jsoniter.Marshal(cfg) 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. // AuthSourceFile contains information of an authentication source file.
type AuthSourceFile struct { type AuthSourceFile struct {
abspath string abspath string
@ -173,6 +189,8 @@ func (s *LoginSource) BeforeSet(colName string, val xorm.Cell) {
s.Cfg = new(SMTPConfig) s.Cfg = new(SMTPConfig)
case LOGIN_PAM: case LOGIN_PAM:
s.Cfg = new(PAMConfig) s.Cfg = new(PAMConfig)
case LOGIN_GITHUB:
s.Cfg = new(GITHUBConfig)
default: default:
panic("unrecognized login source type: " + com.ToStr(*val)) panic("unrecognized login source type: " + com.ToStr(*val))
} }
@ -208,6 +226,10 @@ func (s *LoginSource) IsPAM() bool {
return s.Type == LOGIN_PAM return s.Type == LOGIN_PAM
} }
func (s *LoginSource) IsGITHUB() bool {
return s.Type == LOGIN_GITHUB
}
func (s *LoginSource) HasTLS() bool { func (s *LoginSource) HasTLS() bool {
return ((s.IsLDAP() || s.IsDLDAP()) && return ((s.IsLDAP() || s.IsDLDAP()) &&
s.LDAP().SecurityProtocol > ldap.SECURITY_PROTOCOL_UNENCRYPTED) || s.LDAP().SecurityProtocol > ldap.SECURITY_PROTOCOL_UNENCRYPTED) ||
@ -248,6 +270,10 @@ func (s *LoginSource) PAM() *PAMConfig {
return s.Cfg.(*PAMConfig) return s.Cfg.(*PAMConfig)
} }
func (s *LoginSource) GITHUB() *GITHUBConfig {
return s.Cfg.(*GITHUBConfig)
}
func CreateLoginSource(source *LoginSource) error { func CreateLoginSource(source *LoginSource) error {
has, err := x.Get(&LoginSource{Name: source.Name}) has, err := x.Get(&LoginSource{Name: source.Name})
if err != nil { if err != nil {
@ -456,6 +482,9 @@ func LoadAuthSources() {
case "pam": case "pam":
loginSource.Type = LOGIN_PAM loginSource.Type = LOGIN_PAM
loginSource.Cfg = &PAMConfig{} loginSource.Cfg = &PAMConfig{}
case "github":
loginSource.Type = LOGIN_GITHUB
loginSource.Cfg = &GITHUBConfig{}
default: default:
log.Fatal(2, "Failed to load authentication source: unknown type '%s'", authType) log.Fatal(2, "Failed to load authentication source: unknown type '%s'", authType)
} }
@ -694,7 +723,33 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon
} }
return user, CreateUser(user) 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) { func remoteUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
if !source.IsActived { if !source.IsActived {
return nil, errors.LoginSourceNotActivated{source.ID} return nil, errors.LoginSourceNotActivated{source.ID}
@ -707,6 +762,8 @@ func remoteUserLogin(user *User, login, password string, source *LoginSource, au
return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister) return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
case LOGIN_PAM: case LOGIN_PAM:
return LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister) 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} return nil, errors.InvalidLoginSourceType{source.Type}

2
models/models.go

@ -345,6 +345,8 @@ func ImportDatabase(dirPath string, verbose bool) (err error) {
bean.Cfg = new(SMTPConfig) bean.Cfg = new(SMTPConfig)
case LOGIN_PAM: case LOGIN_PAM:
bean.Cfg = new(PAMConfig) bean.Cfg = new(PAMConfig)
case LOGIN_GITHUB:
bean.Cfg = new(GITHUBConfig)
default: default:
return fmt.Errorf("unrecognized login source type:: %v", tp) return fmt.Errorf("unrecognized login source type:: %v", tp)
} }

45
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
}

3
pkg/form/auth.go

@ -11,7 +11,7 @@ import (
type Authentication struct { type Authentication struct {
ID int64 ID int64
Type int `binding:"Range(2,5)"` Type int `binding:"Range(2,6)"`
Name string `binding:"Required;MaxSize(30)"` Name string `binding:"Required;MaxSize(30)"`
Host string Host string
Port int Port int
@ -40,6 +40,7 @@ type Authentication struct {
TLS bool TLS bool
SkipVerify bool SkipVerify bool
PAMServiceName string PAMServiceName string
GithubApiEndpoint string `binding:Required;Url`
} }
func (f *Authentication) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { func (f *Authentication) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {

6
public/js/gogs.js

@ -851,6 +851,7 @@ function initAdmin() {
$('.dldap').hide(); $('.dldap').hide();
$('.smtp').hide(); $('.smtp').hide();
$('.pam').hide(); $('.pam').hide();
$('.github').hide();
$('.has-tls').hide(); $('.has-tls').hide();
var authType = $(this).val(); var authType = $(this).val();
@ -868,7 +869,10 @@ function initAdmin() {
case '5': // LDAP case '5': // LDAP
$('.dldap').show(); $('.dldap').show();
break; break;
} case '6': //GITHUB
$('.github').show();
break;
}
if (authType == '2' || authType == '5') { if (authType == '2' || authType == '5') {
onSecurityProtocolChange() onSecurityProtocolChange()

18
routes/admin/auths.go

@ -7,15 +7,16 @@ package admin
import ( import (
"fmt" "fmt"
"strings"
"github.com/Unknwon/com" "github.com/Unknwon/com"
"github.com/go-xorm/core" "github.com/go-xorm/core"
log "gopkg.in/clog.v1"
"github.com/gogs/gogs/models" "github.com/gogs/gogs/models"
"github.com/gogs/gogs/pkg/auth/ldap" "github.com/gogs/gogs/pkg/auth/ldap"
"github.com/gogs/gogs/pkg/context" "github.com/gogs/gogs/pkg/context"
"github.com/gogs/gogs/pkg/form" "github.com/gogs/gogs/pkg/form"
"github.com/gogs/gogs/pkg/setting" "github.com/gogs/gogs/pkg/setting"
log "gopkg.in/clog.v1"
) )
const ( const (
@ -51,6 +52,7 @@ var (
{models.LoginNames[models.LOGIN_DLDAP], models.LOGIN_DLDAP}, {models.LoginNames[models.LOGIN_DLDAP], models.LOGIN_DLDAP},
{models.LoginNames[models.LOGIN_SMTP], models.LOGIN_SMTP}, {models.LoginNames[models.LOGIN_SMTP], models.LOGIN_SMTP},
{models.LoginNames[models.LOGIN_PAM], models.LOGIN_PAM}, {models.LoginNames[models.LOGIN_PAM], models.LOGIN_PAM},
{models.LoginNames[models.LOGIN_GITHUB], models.LOGIN_GITHUB},
} }
securityProtocols = []dropdownItem{ securityProtocols = []dropdownItem{
{models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED], ldap.SECURITY_PROTOCOL_UNENCRYPTED}, {models.SecurityProtocolNames[ldap.SECURITY_PROTOCOL_UNENCRYPTED], ldap.SECURITY_PROTOCOL_UNENCRYPTED},
@ -137,6 +139,10 @@ func NewAuthSourcePost(c *context.Context, f form.Authentication) {
config = &models.PAMConfig{ config = &models.PAMConfig{
ServiceName: f.PAMServiceName, ServiceName: f.PAMServiceName,
} }
case models.LOGIN_GITHUB:
config = &models.GITHUBConfig{
ApiEndpoint: f.GithubApiEndpoint,
}
default: default:
c.Error(400) c.Error(400)
return return
@ -218,6 +224,14 @@ func EditAuthSourcePost(c *context.Context, f form.Authentication) {
config = &models.PAMConfig{ config = &models.PAMConfig{
ServiceName: f.PAMServiceName, ServiceName: f.PAMServiceName,
} }
case models.LOGIN_GITHUB:
var apiEndpoint = f.GithubApiEndpoint
if !strings.HasSuffix(apiEndpoint, "/") {
apiEndpoint += "/"
}
config = &models.GITHUBConfig{
ApiEndpoint: apiEndpoint,
}
default: default:
c.Error(400) c.Error(400)
return return

8
templates/admin/auth/edit.tmpl

@ -168,6 +168,14 @@
</div> </div>
{{end}} {{end}}
<!-- GitHub OAuth 2.0 -->
{{if .Source.IsGITHUB}}
{{ $cfg:=.Source.GITHUB }}
<div class="required field">
<label for="github_api_endpoint">{{.i18n.Tr "admin.auths.github_api_endpoint"}}</label>
<input id="github_api_endpoint" name="github_api_endpoint" value="{{$cfg.ApiEndpoint}}" placeholder="e.g. https://api.github.com" required>
</div>
{{end}}
<div class="inline field {{if not .Source.IsSMTP}}hide{{end}}"> <div class="inline field {{if not .Source.IsSMTP}}hide{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.enable_tls"}}</strong></label> <label><strong>{{.i18n.Tr "admin.auths.enable_tls"}}</strong></label>

7
templates/admin/auth/new.tmpl

@ -157,7 +157,11 @@
<label for="pam_service_name">{{.i18n.Tr "admin.auths.pam_service_name"}}</label> <label for="pam_service_name">{{.i18n.Tr "admin.auths.pam_service_name"}}</label>
<input id="pam_service_name" name="pam_service_name" value="{{.pam_service_name}}" /> <input id="pam_service_name" name="pam_service_name" value="{{.pam_service_name}}" />
</div> </div>
<!-- GitHub Oauth 2.0 -->
<div class="github required field {{if not (eq .type 6)}}hide{{end}}">
<label for="github_api_endpoint">{{.i18n.Tr "admin.auths.github_api_endpoint"}}</label>
<input id="github_api_endpoint" name="github_api_endpoint" value="{{.github_api_endpoint}}" placeholder="e.g. https://api.github.com" />
</div>
<div class="ldap field"> <div class="ldap field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.attributes_in_bind"}}</strong></label> <label><strong>{{.i18n.Tr "admin.auths.attributes_in_bind"}}</strong></label>
@ -183,6 +187,7 @@
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<button class="ui green button">{{.i18n.Tr "admin.auths.new"}}</button> <button class="ui green button">{{.i18n.Tr "admin.auths.new"}}</button>
</div> </div>

Loading…
Cancel
Save