@@ -6,11 +6,13 @@ go: | |||||
- 1.4 | - 1.4 | ||||
- tip | - tip | ||||
sudo: false | |||||
before_install: | |||||
- sudo apt-get update -qq | |||||
- sudo apt-get install -y libpam-dev | |||||
script: go build -v | script: go build -v | ||||
notifications: | notifications: | ||||
email: | email: | ||||
- u@gogs.io | - u@gogs.io | ||||
slack: gophercn:o5pSanyTeNhnfYc3QnG0X7Wx | |||||
slack: gophercn:o5pSanyTeNhnfYc3QnG0X7Wx |
@@ -102,7 +102,7 @@ func runServ(c *cli.Context) { | |||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND") | cmd := os.Getenv("SSH_ORIGINAL_COMMAND") | ||||
if cmd == "" { | if cmd == "" { | ||||
println("Hi", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.") | |||||
fmt.Printf("Hi, %s! You've successfully authenticated, but Gogs does not provide shell access.\n", user.Name) | |||||
if user.IsAdmin { | if user.IsAdmin { | ||||
println("If this is unexpected, please log in with password and setup Gogs under another user.") | println("If this is unexpected, please log in with password and setup Gogs under another user.") | ||||
} | } | ||||
@@ -619,6 +619,7 @@ auths.smtp_auth = SMTP Authorization Type | |||||
auths.smtphost = SMTP Host | auths.smtphost = SMTP Host | ||||
auths.smtpport = SMTP Port | auths.smtpport = SMTP Port | ||||
auths.enable_tls = Enable TLS Encryption | auths.enable_tls = Enable TLS Encryption | ||||
auths.pam_service_name = PAM Service Name | |||||
auths.enable_auto_register = Enable Auto Registration | auths.enable_auto_register = Enable Auto Registration | ||||
auths.tips = Tips | auths.tips = Tips | ||||
auths.edit = Edit Authorization Setting | auths.edit = Edit Authorization Setting | ||||
@@ -17,6 +17,7 @@ import ( | |||||
"github.com/go-xorm/xorm" | "github.com/go-xorm/xorm" | ||||
"github.com/gogits/gogs/modules/auth/ldap" | "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/log" | ||||
"github.com/gogits/gogs/modules/uuid" | "github.com/gogits/gogs/modules/uuid" | ||||
) | ) | ||||
@@ -28,6 +29,7 @@ const ( | |||||
PLAIN | PLAIN | ||||
LDAP | LDAP | ||||
SMTP | SMTP | ||||
PAM | |||||
) | ) | ||||
var ( | var ( | ||||
@@ -39,12 +41,14 @@ var ( | |||||
var LoginTypes = map[LoginType]string{ | var LoginTypes = map[LoginType]string{ | ||||
LDAP: "LDAP", | LDAP: "LDAP", | ||||
SMTP: "SMTP", | SMTP: "SMTP", | ||||
PAM: "PAM", | |||||
} | } | ||||
// Ensure structs implemented interface. | // Ensure structs implemented interface. | ||||
var ( | var ( | ||||
_ core.Conversion = &LDAPConfig{} | _ core.Conversion = &LDAPConfig{} | ||||
_ core.Conversion = &SMTPConfig{} | _ core.Conversion = &SMTPConfig{} | ||||
_ core.Conversion = &PAMConfig{} | |||||
) | ) | ||||
type LDAPConfig struct { | type LDAPConfig struct { | ||||
@@ -74,6 +78,18 @@ func (cfg *SMTPConfig) ToDB() ([]byte, error) { | |||||
return json.Marshal(cfg) | 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 { | type LoginSource struct { | ||||
Id int64 | Id int64 | ||||
Type LoginType | Type LoginType | ||||
@@ -97,6 +113,10 @@ func (source *LoginSource) SMTP() *SMTPConfig { | |||||
return source.Cfg.(*SMTPConfig) | return source.Cfg.(*SMTPConfig) | ||||
} | } | ||||
func (source *LoginSource) PAM() *PAMConfig { | |||||
return source.Cfg.(*PAMConfig) | |||||
} | |||||
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { | func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { | ||||
if colName == "type" { | if colName == "type" { | ||||
ty := (*val).(int64) | ty := (*val).(int64) | ||||
@@ -105,6 +125,8 @@ func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) { | |||||
source.Cfg = new(LDAPConfig) | source.Cfg = new(LDAPConfig) | ||||
case SMTP: | case SMTP: | ||||
source.Cfg = new(SMTPConfig) | source.Cfg = new(SMTPConfig) | ||||
case PAM: | |||||
source.Cfg = new(PAMConfig) | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -169,7 +191,7 @@ func UserSignIn(uname, passwd string) (*User, error) { | |||||
// For plain login, user must exist to reach this line. | // For plain login, user must exist to reach this line. | ||||
// Now verify password. | // Now verify password. | ||||
if u.LoginType == PLAIN { | if u.LoginType == PLAIN { | ||||
if !u.ValidtePassword(passwd) { | |||||
if !u.ValidatePassword(passwd) { | |||||
return nil, ErrUserNotExist | return nil, ErrUserNotExist | ||||
} | } | ||||
return u, nil | return u, nil | ||||
@@ -197,6 +219,13 @@ func UserSignIn(uname, passwd string) (*User, error) { | |||||
return u, nil | return u, nil | ||||
} | } | ||||
log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err) | 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) | return LoginUserLdapSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*LDAPConfig), false) | ||||
case SMTP: | case SMTP: | ||||
return LoginUserSMTPSource(u, u.LoginName, passwd, source.Id, source.Cfg.(*SMTPConfig), false) | 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 | return nil, ErrUnsupportedLoginType | ||||
} | } | ||||
@@ -359,3 +390,33 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP | |||||
err := CreateUser(u) | err := CreateUser(u) | ||||
return u, err | 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 | |||||
} |
@@ -40,6 +40,7 @@ var ( | |||||
ErrRepoFileNotLoaded = errors.New("Repository file not loaded") | ErrRepoFileNotLoaded = errors.New("Repository file not loaded") | ||||
ErrMirrorNotExist = errors.New("Mirror does not exist") | ErrMirrorNotExist = errors.New("Mirror does not exist") | ||||
ErrInvalidReference = errors.New("Invalid reference specified") | ErrInvalidReference = errors.New("Invalid reference specified") | ||||
ErrNameEmpty = errors.New("Name is empty") | |||||
) | ) | ||||
var ( | var ( | ||||
@@ -242,10 +243,11 @@ func (repo *Repository) CloneLink() (cl CloneLink, err error) { | |||||
if err = repo.GetOwner(); err != nil { | if err = repo.GetOwner(); err != nil { | ||||
return cl, err | return cl, err | ||||
} | } | ||||
if setting.SSHPort != 22 { | if setting.SSHPort != 22 { | ||||
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.Domain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName) | |||||
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName) | |||||
} else { | } else { | ||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.Domain, repo.Owner.LowerName, repo.LowerName) | |||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.LowerName, repo.LowerName) | |||||
} | } | ||||
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.LowerName, repo.LowerName) | cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.LowerName, repo.LowerName) | ||||
return cl, nil | return cl, nil | ||||
@@ -258,7 +260,11 @@ var ( | |||||
// IsUsableName checks if name is reserved or pattern of name is not allowed. | // IsUsableName checks if name is reserved or pattern of name is not allowed. | ||||
func IsUsableName(name string) error { | func IsUsableName(name string) error { | ||||
name = strings.ToLower(name) | |||||
name = strings.TrimSpace(strings.ToLower(name)) | |||||
if utf8.RuneCountInString(name) == 0 { | |||||
return ErrNameEmpty | |||||
} | |||||
for i := range reservedNames { | for i := range reservedNames { | ||||
if name == reservedNames[i] { | if name == reservedNames[i] { | ||||
return ErrNameReserved{name} | return ErrNameReserved{name} | ||||
@@ -143,8 +143,8 @@ func (u *User) EncodePasswd() { | |||||
u.Passwd = fmt.Sprintf("%x", newPasswd) | u.Passwd = fmt.Sprintf("%x", newPasswd) | ||||
} | } | ||||
// ValidtePassword checks if given password matches the one belongs to the user. | |||||
func (u *User) ValidtePassword(passwd string) bool { | |||||
// ValidatePassword checks if given password matches the one belongs to the user. | |||||
func (u *User) ValidatePassword(passwd string) bool { | |||||
newUser := &User{Passwd: passwd, Salt: u.Salt} | newUser := &User{Passwd: passwd, Salt: u.Salt} | ||||
newUser.EncodePasswd() | newUser.EncodePasswd() | ||||
return u.Passwd == newUser.Passwd | return u.Passwd == newUser.Passwd | ||||
@@ -30,6 +30,7 @@ type AuthenticationForm struct { | |||||
SMTPPort int `form:"smtp_port"` | SMTPPort int `form:"smtp_port"` | ||||
TLS bool `form:"tls"` | TLS bool `form:"tls"` | ||||
AllowAutoRegister bool `form:"allowautoregister"` | AllowAutoRegister bool `form:"allowautoregister"` | ||||
PAMServiceName string | |||||
} | } | ||||
func (f *AuthenticationForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { | 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") | |||||
} |
@@ -139,6 +139,13 @@ func (ctx *Context) Handle(status int, title string, err error) { | |||||
ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status))) | ctx.HTML(status, base.TplName(fmt.Sprintf("status/%d", status))) | ||||
} | } | ||||
func (ctx *Context) HandleText(status int, title string) { | |||||
if (status / 100 == 4) || (status / 100 == 5) { | |||||
log.Error(4, "%s", title) | |||||
} | |||||
ctx.RenderData(status, []byte(title)) | |||||
} | |||||
func (ctx *Context) HandleAPI(status int, obj interface{}) { | func (ctx *Context) HandleAPI(status int, obj interface{}) { | ||||
var message string | var message string | ||||
if err, ok := obj.(error); ok { | if err, ok := obj.(error); ok { | ||||
@@ -53,6 +53,7 @@ var ( | |||||
HttpAddr, HttpPort string | HttpAddr, HttpPort string | ||||
DisableSSH bool | DisableSSH bool | ||||
SSHPort int | SSHPort int | ||||
SSHDomain string | |||||
OfflineMode bool | OfflineMode bool | ||||
DisableRouterLog bool | DisableRouterLog bool | ||||
CertFile, KeyFile string | CertFile, KeyFile string | ||||
@@ -232,6 +233,7 @@ func NewConfigContext() { | |||||
HttpAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") | HttpAddr = sec.Key("HTTP_ADDR").MustString("0.0.0.0") | ||||
HttpPort = sec.Key("HTTP_PORT").MustString("3000") | HttpPort = sec.Key("HTTP_PORT").MustString("3000") | ||||
DisableSSH = sec.Key("DISABLE_SSH").MustBool() | DisableSSH = sec.Key("DISABLE_SSH").MustBool() | ||||
SSHDomain = sec.Key("SSH_DOMAIN").MustString(Domain) | |||||
SSHPort = sec.Key("SSH_PORT").MustInt(22) | SSHPort = sec.Key("SSH_PORT").MustInt(22) | ||||
OfflineMode = sec.Key("OFFLINE_MODE").MustBool() | OfflineMode = sec.Key("OFFLINE_MODE").MustBool() | ||||
DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() | DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() | ||||
@@ -753,10 +753,17 @@ function initAdmin() { | |||||
if (v == 2) { | if (v == 2) { | ||||
$('.ldap').toggleShow(); | $('.ldap').toggleShow(); | ||||
$('.smtp').toggleHide(); | $('.smtp').toggleHide(); | ||||
$('.pam').toggleHide(); | |||||
} | } | ||||
if (v == 3) { | if (v == 3) { | ||||
$('.smtp').toggleShow(); | $('.smtp').toggleShow(); | ||||
$('.ldap').toggleHide(); | $('.ldap').toggleHide(); | ||||
$('.pam').toggleHide(); | |||||
} | |||||
if (v == 4) { | |||||
$('.pam').toggleShow(); | |||||
$('.smtp').toggleHide(); | |||||
$('.ldap').toggleHide(); | |||||
} | } | ||||
}); | }); | ||||
@@ -25,6 +25,11 @@ The register and sign-in page style | |||||
.form-label { | .form-label { | ||||
width: 160px; | width: 160px; | ||||
} | } | ||||
.chk-label { | |||||
width: auto; | |||||
text-align: left; | |||||
margin-left: 176px; | |||||
} | |||||
.alert{ | .alert{ | ||||
margin:0 30px 24px 30px; | margin:0 30px 24px 30px; | ||||
} | } | ||||
@@ -60,4 +65,4 @@ The register and sign-in page style | |||||
background-color: #FFF; | background-color: #FFF; | ||||
margin-left: -15px; | margin-left: -15px; | ||||
} | } | ||||
} | |||||
} |
@@ -84,6 +84,10 @@ func NewAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) { | |||||
Port: form.SMTPPort, | Port: form.SMTPPort, | ||||
TLS: form.TLS, | TLS: form.TLS, | ||||
} | } | ||||
case models.PAM: | |||||
u = &models.PAMConfig{ | |||||
ServiceName: form.PAMServiceName, | |||||
} | |||||
default: | default: | ||||
ctx.Error(400) | ctx.Error(400) | ||||
return | return | ||||
@@ -166,6 +170,10 @@ func EditAuthSourcePost(ctx *middleware.Context, form auth.AuthenticationForm) { | |||||
Port: form.SMTPPort, | Port: form.SMTPPort, | ||||
TLS: form.TLS, | TLS: form.TLS, | ||||
} | } | ||||
case models.PAM: | |||||
config = &models.PAMConfig{ | |||||
ServiceName: form.PAMServiceName, | |||||
} | |||||
default: | default: | ||||
ctx.Error(400) | ctx.Error(400) | ||||
return | return | ||||
@@ -164,7 +164,7 @@ func MigrateRepo(ctx *middleware.Context, form auth.MigrateRepoForm) { | |||||
} | } | ||||
return | return | ||||
} | } | ||||
if !u.ValidtePassword(ctx.Query("password")) { | |||||
if !u.ValidatePassword(ctx.Query("password")) { | |||||
ctx.HandleAPI(422, "Username or password is not correct.") | ctx.HandleAPI(422, "Username or password is not correct.") | ||||
return | return | ||||
} | } | ||||
@@ -96,12 +96,12 @@ func Http(ctx *middleware.Context) { | |||||
// FIXME: middlewares/context.go did basic auth check already, | // FIXME: middlewares/context.go did basic auth check already, | ||||
// maybe could use that one. | // maybe could use that one. | ||||
if len(auths) != 2 || auths[0] != "Basic" { | if len(auths) != 2 || auths[0] != "Basic" { | ||||
ctx.Handle(401, "no basic auth and digit auth", nil) | |||||
ctx.HandleText(401, "no basic auth and digit auth") | |||||
return | return | ||||
} | } | ||||
authUsername, authPasswd, err = base.BasicAuthDecode(auths[1]) | authUsername, authPasswd, err = base.BasicAuthDecode(auths[1]) | ||||
if err != nil { | if err != nil { | ||||
ctx.Handle(401, "no basic auth and digit auth", nil) | |||||
ctx.HandleText(401, "no basic auth and digit auth") | |||||
return | return | ||||
} | } | ||||
@@ -116,7 +116,7 @@ func Http(ctx *middleware.Context) { | |||||
token, err := models.GetAccessTokenBySha(authUsername) | token, err := models.GetAccessTokenBySha(authUsername) | ||||
if err != nil { | if err != nil { | ||||
if err == models.ErrAccessTokenNotExist { | if err == models.ErrAccessTokenNotExist { | ||||
ctx.Handle(401, "invalid token", nil) | |||||
ctx.HandleText(401, "invalid token") | |||||
} else { | } else { | ||||
ctx.Handle(500, "GetAccessTokenBySha", err) | ctx.Handle(500, "GetAccessTokenBySha", err) | ||||
} | } | ||||
@@ -138,23 +138,23 @@ func Http(ctx *middleware.Context) { | |||||
has, err := models.HasAccess(authUser, repo, tp) | has, err := models.HasAccess(authUser, repo, tp) | ||||
if err != nil { | if err != nil { | ||||
ctx.Handle(401, "no basic auth and digit auth", nil) | |||||
ctx.HandleText(401, "no basic auth and digit auth") | |||||
return | return | ||||
} else if !has { | } else if !has { | ||||
if tp == models.ACCESS_MODE_READ { | if tp == models.ACCESS_MODE_READ { | ||||
has, err = models.HasAccess(authUser, repo, models.ACCESS_MODE_WRITE) | has, err = models.HasAccess(authUser, repo, models.ACCESS_MODE_WRITE) | ||||
if err != nil || !has { | if err != nil || !has { | ||||
ctx.Handle(401, "no basic auth and digit auth", nil) | |||||
ctx.HandleText(401, "no basic auth and digit auth") | |||||
return | return | ||||
} | } | ||||
} else { | } else { | ||||
ctx.Handle(401, "no basic auth and digit auth", nil) | |||||
ctx.HandleText(401, "no basic auth and digit auth") | |||||
return | return | ||||
} | } | ||||
} | } | ||||
if !isPull && repo.IsMirror { | if !isPull && repo.IsMirror { | ||||
ctx.Handle(401, "can't push to mirror", nil) | |||||
ctx.HandleText(401, "can't push to mirror") | |||||
return | return | ||||
} | } | ||||
} | } | ||||
@@ -33,7 +33,7 @@ LOGFILE=${GOGS_HOME}/log/gogs.log | |||||
RETVAL=0 | RETVAL=0 | ||||
# Read configuration from /etc/sysconfig/gogs to override defaults | # Read configuration from /etc/sysconfig/gogs to override defaults | ||||
[ -r /etc/sysconfig/$NAME ] && ./etc/sysconfig/$NAME | |||||
[ -r /etc/sysconfig/$NAME ] && . /etc/sysconfig/$NAME | |||||
# Don't do anything if nothing is installed | # Don't do anything if nothing is installed | ||||
[ -x ${GOGS_PATH} ] || exit 0 | [ -x ${GOGS_PATH} ] || exit 0 | ||||
@@ -91,6 +91,12 @@ | |||||
<label class="req" for="smtp_port">{{.i18n.Tr "admin.auths.smtpport"}}</label> | <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}}" /> | <input class="ipt ipt-large ipt-radius {{if .Err_SmtpPort}}ipt-error{{end}}" id="smtp_port" name="smtp_port" value="{{.Source.SMTP.Port}}" /> | ||||
</div> | </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}} | {{end}} | ||||
<div class="field"> | <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}}" /> | <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> | </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="field"> | ||||
<div class="smtp hidden"> | <div class="smtp hidden"> | ||||
<label></label> | <label></label> | ||||
@@ -2,24 +2,26 @@ | |||||
{{template "ng/base/header" .}} | {{template "ng/base/header" .}} | ||||
<div id="setting-wrapper" class="main-wrapper"> | <div id="setting-wrapper" class="main-wrapper"> | ||||
<div id="org-setting" class="container clear"> | <div id="org-setting" class="container clear"> | ||||
{{template "explore/nav" .}} | |||||
{{template "explore/nav" .}} | |||||
<div class="grid-4-5 left"> | <div class="grid-4-5 left"> | ||||
<div class="setting-content"> | <div class="setting-content"> | ||||
<div id="org-repo-list"> | |||||
{{range .Repos}} | |||||
<div class="org-repo-item"> | |||||
<ul class="org-repo-status right"> | |||||
<li><i class="octicon octicon-star"></i> {{.NumStars}}</li> | |||||
<li><i class="octicon octicon-git-branch"></i> {{.NumForks}}</li> | |||||
</ul> | |||||
<h2><a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">{{.Name}}</a></h2> | |||||
<p class="org-repo-description">{{.Description}}</p> | |||||
<p class="org-repo-updated">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p> | |||||
</div> | |||||
{{end}} | |||||
</div> | |||||
</div> | |||||
<div id="org-repo-list"> | |||||
{{range .Repos}} | |||||
<div class="org-repo-item"> | |||||
<ul class="org-repo-status right"> | |||||
<li><i class="octicon octicon-star"></i> {{.NumStars}}</li> | |||||
<li><i class="octicon octicon-git-branch"></i> {{.NumForks}}</li> | |||||
</ul> | |||||
<h2> | |||||
<a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">{{.Owner.Name}} / {{.Name}}</a> | |||||
</h2> | |||||
<p class="org-repo-description">{{.Description}}</p> | |||||
<p class="org-repo-updated">{{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Updated $.i18n.Lang}}</p> | |||||
</div> | |||||
{{end}} | |||||
</div> | |||||
</div> | |||||
</div> | </div> | ||||
</div> | </div> | ||||
</div> | </div> | ||||
{{template "ng/base/footer" .}} | |||||
{{template "ng/base/footer" .}} |
@@ -17,8 +17,9 @@ | |||||
</div> | </div> | ||||
{{if not .IsSocialLogin}} | {{if not .IsSocialLogin}} | ||||
<div class="field"> | <div class="field"> | ||||
<span class="form-label"></span> | |||||
<input class="ipt-chk" id="remember" name="remember" type="checkbox"/> <strong>{{.i18n.Tr "auth.remember_me"}}</strong> | |||||
<label class="chk-label"> | |||||
<input class="ipt-chk" id="remember" name="remember" type="checkbox"/> <strong>{{.i18n.Tr "auth.remember_me"}}</strong> | |||||
</label> | |||||
</div> | </div> | ||||
{{end}} | {{end}} | ||||
<div class="field"> | <div class="field"> | ||||
@@ -41,4 +42,4 @@ | |||||
</div> | </div> | ||||
</form> | </form> | ||||
</div> | </div> | ||||
{{template "ng/base/footer" .}} | |||||
{{template "ng/base/footer" .}} |