@@ -6,11 +6,13 @@ go: | |||
- 1.4 | |||
- tip | |||
sudo: false | |||
before_install: | |||
- sudo apt-get update -qq | |||
- sudo apt-get install -y libpam-dev | |||
script: go build -v | |||
notifications: | |||
email: | |||
- u@gogs.io | |||
slack: gophercn:o5pSanyTeNhnfYc3QnG0X7Wx | |||
slack: gophercn:o5pSanyTeNhnfYc3QnG0X7Wx |
@@ -619,6 +619,7 @@ auths.smtp_auth = SMTP Authorization Type | |||
auths.smtphost = SMTP Host | |||
auths.smtpport = SMTP Port | |||
auths.enable_tls = Enable TLS Encryption | |||
auths.pam_service_name = PAM Service Name | |||
auths.enable_auto_register = Enable Auto Registration | |||
auths.tips = Tips | |||
auths.edit = Edit Authorization Setting |
@@ -17,6 +17,7 @@ import ( | |||
"github.com/go-xorm/xorm" | |||
"github.com/gogits/gogs/modules/auth/ldap" | |||
"github.com/gogits/gogs/modules/auth/pam" | |||
"github.com/gogits/gogs/modules/log" | |||
"github.com/gogits/gogs/modules/uuid" | |||
) | |||
@@ -28,6 +29,7 @@ const ( | |||
PLAIN | |||
LDAP | |||
SMTP | |||
PAM | |||
) | |||
var ( | |||
@@ -39,12 +41,14 @@ var ( | |||
var LoginTypes = map[LoginType]string{ | |||
LDAP: "LDAP", | |||
SMTP: "SMTP", | |||
PAM: "PAM", | |||
} | |||
// Ensure structs implemented interface. | |||
var ( | |||
_ core.Conversion = &LDAPConfig{} | |||
_ core.Conversion = &SMTPConfig{} | |||
_ core.Conversion = &PAMConfig{} | |||
) | |||
type LDAPConfig struct { | |||
@@ -74,6 +78,18 @@ func (cfg *SMTPConfig) ToDB() ([]byte, error) { | |||
return json.Marshal(cfg) | |||
} | |||
type PAMConfig struct { | |||
ServiceName string // pam service (e.g. system-auth) | |||
} | |||
func (cfg *PAMConfig) FromDB(bs []byte) error { | |||
return json.Unmarshal(bs, &cfg) | |||
} | |||
func (cfg *PAMConfig) ToDB() ([]byte, error) { | |||
return json.Marshal(cfg) | |||
} | |||
type LoginSource struct { | |||
Id int64 | |||
Type LoginType | |||
@@ -97,6 +113,10 @@ func (source *LoginSource) SMTP() *SMTPConfig { | |||
return source.Cfg.(*SMTPConfig) | |||
} | |||
func (source *LoginSource) PAM() *PAMConfig { | |||
return source.Cfg.(*PAMConfig) | |||
} | |||
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { | |||
if colName == "type" { | |||
ty := (*val).(int64) | |||
@@ -105,6 +125,8 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { | |||
source.Cfg = new(LDAPConfig) | |||
case SMTP: | |||
source.Cfg = new(SMTPConfig) | |||
case PAM: | |||
source.Cfg = new(PAMConfig) | |||
} | |||
} | |||
} | |||
@@ -197,6 +219,13 @@ func UserSignIn(uname, passwd string) (*User, error) { | |||
return u, nil | |||
} | |||
log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err) | |||
} else if source.Type == PAM { | |||
u, err := LoginUserPAMSource(nil, uname, passwd, | |||
source.Id, source.Cfg.(*PAMConfig), true) | |||
if err == nil { | |||
return u, nil | |||
} | |||
log.Warn("Fail to login(%s) by PAM(%s): %v", uname, source.Name, err) | |||
} | |||
} | |||
@@ -218,6 +247,8 @@ func UserSignIn(uname, passwd string) (*User, error) { | |||
return LoginUserLdapSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*LDAPConfig), false) | |||
case SMTP: | |||
return LoginUserSMTPSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*SMTPConfig), false) | |||
case PAM: | |||
return LoginUserPAMSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*PAMConfig), false) | |||
} | |||
return nil, ErrUnsupportedLoginType | |||
} | |||
@@ -359,3 +390,33 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP | |||
err := CreateUser(u) | |||
return u, err | |||
} | |||
// Query if name/passwd can login against PAM | |||
// Create a local user if success | |||
// Return the same LoginUserPlain semantic | |||
func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMConfig, autoRegister bool) (*User, error) { | |||
if err := pam.PAMAuth(cfg.ServiceName, name, passwd); err != nil { | |||
if strings.Contains(err.Error(), "Authentication failure") { | |||
return nil, ErrUserNotExist | |||
} | |||
return nil, err | |||
} | |||
if !autoRegister { | |||
return u, nil | |||
} | |||
// fake a local user creation | |||
u = &User{ | |||
LowerName: strings.ToLower(name), | |||
Name: strings.ToLower(name), | |||
LoginType: PAM, | |||
LoginSource: sourceId, | |||
LoginName: name, | |||
IsActive: true, | |||
Passwd: passwd, | |||
Email: name, | |||
} | |||
err := CreateUser(u) | |||
return u, err | |||
} |
@@ -30,6 +30,7 @@ type AuthenticationForm struct { | |||
SMTPPort int `form:"smtp_port"` | |||
TLS bool `form:"tls"` | |||
AllowAutoRegister bool `form:"allowautoregister"` | |||
PAMServiceName string | |||
} | |||
func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { |
@@ -0,0 +1,35 @@ | |||
// +build !windows | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package pam | |||
import ( | |||
"errors" | |||
"github.com/msteinert/pam" | |||
) | |||
func PAMAuth(serviceName, userName, passwd string) error { | |||
t, err := pam.StartFunc(serviceName, userName, func(s pam.Style, msg string) (string, error) { | |||
switch s { | |||
case pam.PromptEchoOff: | |||
return passwd, nil | |||
case pam.PromptEchoOn, pam.ErrorMsg, pam.TextInfo: | |||
return "", nil | |||
} | |||
return "", errors.New("Unrecognized PAM message style") | |||
}) | |||
if err != nil { | |||
return err | |||
} | |||
if err = t.Authenticate(0); err != nil { | |||
return err | |||
} | |||
return nil | |||
} |
@@ -0,0 +1,15 @@ | |||
// +build windows | |||
// Copyright 2014 The Gogs Authors. All rights reserved. | |||
// Use of this source code is governed by a MIT-style | |||
// license that can be found in the LICENSE file. | |||
package pam | |||
import ( | |||
"errors" | |||
) | |||
func PAMAuth(serviceName, userName, passwd string) error { | |||
return errors.New("PAM not supported") | |||
} |
@@ -753,10 +753,17 @@ function initAdmin() { | |||
if (v == 2) { | |||
$('.ldap').toggleShow(); | |||
$('.smtp').toggleHide(); | |||
$('.pam').toggleHide(); | |||
} | |||
if (v == 3) { | |||
$('.smtp').toggleShow(); | |||
$('.ldap').toggleHide(); | |||
$('.pam').toggleHide(); | |||
} | |||
if (v == 4) { | |||
$('.pam').toggleShow(); | |||
$('.smtp').toggleHide(); | |||
$('.ldap').toggleHide(); | |||
} | |||
}); | |||
@@ -84,6 +84,10 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) { | |||
Port: form.SMTPPort, | |||
TLS: form.TLS, | |||
} | |||
case models.PAM: | |||
u = &models.PAMConfig{ | |||
ServiceName: form.PAMServiceName, | |||
} | |||
default: | |||
ctx.Error(400) | |||
return | |||
@@ -166,6 +170,10 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) { | |||
Port: form.SMTPPort, | |||
TLS: form.TLS, | |||
} | |||
case models.PAM: | |||
config = &models.PAMConfig{ | |||
ServiceName: form.PAMServiceName, | |||
} | |||
default: | |||
ctx.Error(400) | |||
return |
@@ -91,6 +91,12 @@ | |||
<label class="req" for="smtp_port">{{.i18n.Tr "admin.auths.smtpport"}}</label> | |||
<input class="ipt ipt-large ipt-radius {{if .Err_SmtpPort}}ipt-error{{end}}" id="smtp_port" name="smtp_port" value="{{.Source.SMTP.Port}}" /> | |||
</div> | |||
{{else if eq $type 4}} | |||
<div class="field"> | |||
<label class="req" for="pam_service_name">{{.i18n.Tr "admin.auths.pam_service_name"}}</label> | |||
<input class="ipt ipt-large ipt-radius {{if .Err_PAMServiceName}}ipt-error{{end}}" id="pam_service_name" name="pam_service_name" value="{{.Source.PAM.ServiceName}}" /> | |||
</div> | |||
{{end}} | |||
<div class="field"> |
@@ -86,6 +86,12 @@ | |||
<input class="ipt ipt-large ipt-radius {{if .Err_SmtpPort}}ipt-error{{end}}" id="smtp_port" name="smtp_port" value="{{.smtp_port}}" /> | |||
</div> | |||
</div> | |||
<div class="pam hidden"> | |||
<div class="field"> | |||
<label class="req" for="pam_service_name">{{.i18n.Tr "admin.auths.pam_service_name"}}</label> | |||
<input class="ipt ipt-large ipt-radius {{if .Err_PAMServiceName}}ipt-error{{end}}" id="pam_service_name" name="pam_service_name" value="{{.pam_service_name}}" /> | |||
</div> | |||
</div> | |||
<div class="field"> | |||
<div class="smtp hidden"> | |||
<label></label> |