Skip to content

Commit

Permalink
支持ldap登录 (#100)
Browse files Browse the repository at this point in the history
  • Loading branch information
iwannay committed Apr 24, 2021
1 parent d0eb39f commit 8be0d47
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 96 deletions.
9 changes: 9 additions & 0 deletions app/jiacrontab_admin/jiacrontab_admin.ini
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ passwd = xxxxxx
from = [email protected]
use_certificate = false

[ldap]
; 支持: ldap://, ldaps://, ldapi://.
addr = ladp://localhost:1234
disabled_anonymous_query = false
bind_passwd= 123456
bind_userdn = "cn=admin,dc=jdevops,dc=com"
basedn = "dc=jdevops,dc=com"
user_field = uid

[database]
; jiacrontab_admin目前支持的数据库包括sqlite3,mysql,pg
; 注意: mysql,pg 等数据库需要手动建立jiacrontab库
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.14
require (
github.com/Joker/hpp v1.0.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-ldap/ldap/v3 v3.3.0
github.com/gofrs/uuid v3.2.0+incompatible
github.com/google/go-cmp v0.5.1 // indirect
github.com/gopherjs/gopherjs v0.0.0-20190812055157-5d271430af9f // indirect
Expand Down
37 changes: 7 additions & 30 deletions go.sum

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion jiacrontab_admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"jiacrontab/models"
"jiacrontab/pkg/mailer"
"jiacrontab/pkg/rpc"
"time"

"sync/atomic"

Expand All @@ -13,6 +14,7 @@ import (

type Admin struct {
cfg atomic.Value
ldap *Ldap
initAdminUser int32
}

Expand Down Expand Up @@ -58,11 +60,20 @@ func (a *Admin) init() {
HookMode: false,
})
}
a.ldap = &Ldap{
BindUserDn: cfg.Ldap.BindUserdn,
BindPwd: cfg.Ldap.BindPasswd,
BaseOn: cfg.Ldap.Basedn,
UserField: cfg.Ldap.UserField,
Addr: cfg.Ldap.Addr,
DisabledAnonymousQuery: cfg.Ldap.DisabledAnonymousQuery,
Timeout: time.Second * time.Duration(cfg.Ldap.Timeout),
}
}

func (a *Admin) ResetPwd(username string, password string) error {
if username == "" || password == "" {
return errors.New("username or password cannot empty!")
return errors.New("username or password cannot empty")
}
a.init()
user := models.User{
Expand Down
12 changes: 12 additions & 0 deletions jiacrontab_admin/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,22 @@ type databaseOpt struct {
DSN string `opt:"dsn"`
}

type ldapOpt struct {
Addr string `opt:"addr"`
DisabledAnonymousQuery bool `opt:"disabled_anonymous_query"`
Basedn string `opt:"basedn"`
Timeout int `opt:"timeout"`
BindPasswd string `opt:"bind_passwd"`
BindUserdn string `opt:"bind_userdn"`
UserField string `opt:"user_field"`
}

type Config struct {
Mailer *MailerOpt `section:"mail"`
Jwt *JwtOpt `section:"jwt"`
App *AppOpt `section:"app"`
Database *databaseOpt `section:"database"`
Ldap *ldapOpt `section:"ldap"`

CfgPath string
iniFile *ini.File
Expand Down Expand Up @@ -140,6 +151,7 @@ func NewConfig() *Config {
Name: "token",
Expires: 3600,
},
Ldap: &ldapOpt{},
Database: &databaseOpt{},
}
}
Expand Down
124 changes: 124 additions & 0 deletions jiacrontab_admin/ldap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package admin

import (
"fmt"
"jiacrontab/models"
"time"

"errors"

ld "github.com/go-ldap/ldap/v3"
)

type Ldap struct {
BindUserDn string
BindPwd string
BaseOn string
UserField string
Addr string
Timeout time.Duration
fields []map[string]string
queryFields []string
lastSynced time.Time
DisabledAnonymousQuery bool
}

func (l *Ldap) connect() (*ld.Conn, error) {
var err error
conn, err := ld.DialURL(l.Addr)
if err != nil {
return nil, err
}
conn.SetTimeout(l.Timeout)
return conn, nil
}

func (l *Ldap) Login(username string, password string) (*models.User, error) {
err := l.loadLdapFields()
if err != nil {
return nil, err
}

conn, err := l.connect()
if err != nil {
return nil, err
}
defer conn.Close()

if l.DisabledAnonymousQuery {
err := conn.Bind(l.BindUserDn, l.BindPwd)
if err != nil {
return nil, err
}
}
query := ld.NewSearchRequest(
l.BaseOn,
ld.ScopeWholeSubtree,
ld.DerefAlways,
0, 0, false,
fmt.Sprintf("(%v=%v)", l.UserField, username),
l.queryFields, nil,
)
ret, err := conn.Search(query)

if err != nil {
return nil, fmt.Errorf("在Ldap搜索用户失败 - %v", err)
}
if len(ret.Entries) == 0 {
return nil, fmt.Errorf("在Ldap中未查询到对应的用户信息 - %v", err)
}

err = conn.Bind(ret.Entries[0].DN, password)
if err != nil {
return nil, errors.New("帐号或密码不正确")
}
return l.convert(ret.Entries[0])
}

func (l *Ldap) loadLdapFields() error {
var setting models.SysSetting
var list []map[string]string

if time.Since(l.lastSynced).Hours() < 1 {
return nil
}

err := models.DB().Model(&models.SysSetting{}).Where("class=?", 1).Find(&setting).Error
if err != nil {
return err
}
// err = json.Unmarshal(setting.Content, &list)
// if err != nil {
// return err
// }
for _, v := range list {
if v["ldap_field_name"] != "" {
l.fields = append(l.fields, v)
}
}

l.queryFields = []string{"dn"}
for _, v := range l.fields {
l.queryFields = append(l.queryFields, v["ldap_field_name"])
}

return nil
}

func (l *Ldap) convert(ldapUserInfo *ld.Entry) (*models.User, error) {
var userinfo models.User
for _, v := range l.fields {
switch v["local_field_name"] {
case "username":
userinfo.Username = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
case "gender":
userinfo.Gender = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
case "avatar":
userinfo.Avatar = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
case "email":
userinfo.Mail = ldapUserInfo.GetAttributeValue(v["ldap_field_name"])
}
}
err := models.DB().Where("username=?", userinfo.Username).FirstOrCreate(&userinfo).Error
return &userinfo, err
}
99 changes: 50 additions & 49 deletions jiacrontab_admin/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,41 +44,41 @@ func (p *JobsReqParams) Verify(ctx *myctx) error {
}

type EditJobReqParams struct {
JobID uint `json:"jobID"`
Addr string `json:"addr" rule:"required,请填写addr"`
IsSync bool `json:"isSync"`
Name string `json:"name" rule:"required,请填写name"`
Command []string `json:"command" rule:"required,请填写name"`
Code string `json:"code"`
Timeout int `json:"timeout"`
MaxConcurrent uint `json:"maxConcurrent"`
ErrorMailNotify bool `json:"errorMailNotify"`
ErrorAPINotify bool `json:"errorAPINotify"`
ErrorDingdingNotify bool `json:"errorDingdingNotify"`
MailTo []string `json:"mailTo"`
APITo []string `json:"APITo"`
DingdingTo []string `json:"DingdingTo"`
RetryNum int `json:"retryNum"`
WorkDir string `json:"workDir"`
WorkUser string `json:"workUser"`
WorkEnv []string `json:"workEnv"`
WorkIp []string `json:"workIp"`
KillChildProcess bool `json:"killChildProcess"`
DependJobs models.DependJobs `json:"dependJobs"`
Month string `json:"month"`
Weekday string `json:"weekday"`
Day string `json:"day"`
Hour string `json:"hour"`
Minute string `json:"minute"`
Second string `json:"second"`
TimeoutTrigger []string `json:"timeoutTrigger"`
JobID uint `json:"jobID"`
Addr string `json:"addr" rule:"required,请填写addr"`
IsSync bool `json:"isSync"`
Name string `json:"name" rule:"required,请填写name"`
Command []string `json:"command" rule:"required,请填写name"`
Code string `json:"code"`
Timeout int `json:"timeout"`
MaxConcurrent uint `json:"maxConcurrent"`
ErrorMailNotify bool `json:"errorMailNotify"`
ErrorAPINotify bool `json:"errorAPINotify"`
ErrorDingdingNotify bool `json:"errorDingdingNotify"`
MailTo []string `json:"mailTo"`
APITo []string `json:"APITo"`
DingdingTo []string `json:"DingdingTo"`
RetryNum int `json:"retryNum"`
WorkDir string `json:"workDir"`
WorkUser string `json:"workUser"`
WorkEnv []string `json:"workEnv"`
WorkIp []string `json:"workIp"`
KillChildProcess bool `json:"killChildProcess"`
DependJobs models.DependJobs `json:"dependJobs"`
Month string `json:"month"`
Weekday string `json:"weekday"`
Day string `json:"day"`
Hour string `json:"hour"`
Minute string `json:"minute"`
Second string `json:"second"`
TimeoutTrigger []string `json:"timeoutTrigger"`
}

func (p *EditJobReqParams) Verify(ctx *myctx) error {
ts := map[string]bool{
proto.TimeoutTrigger_CallApi: true,
proto.TimeoutTrigger_SendEmail: true,
proto.TimeoutTrigger_Kill: true,
proto.TimeoutTrigger_CallApi: true,
proto.TimeoutTrigger_SendEmail: true,
proto.TimeoutTrigger_Kill: true,
proto.TimeoutTrigger_DingdingWebhook: true,
}

Expand Down Expand Up @@ -228,23 +228,23 @@ func (p *ActionTaskReqParams) Verify(ctx *myctx) error {
}

type EditDaemonJobReqParams struct {
Addr string `json:"addr" rule:"required,请填写addr"`
JobID uint `json:"jobID"`
Name string `json:"name" rule:"required,请填写name"`
MailTo []string `json:"mailTo"`
APITo []string `json:"APITo"`
DingdingTo []string `json:"DingdingTo"`
Command []string `json:"command" rule:"required,请填写command"`
Code string `json:"code"`
WorkUser string `json:"workUser"`
WorkIp []string `json:"workIp"`
WorkEnv []string `json:"workEnv"`
WorkDir string `json:"workDir"`
FailRestart bool `json:"failRestart"`
RetryNum int `json:"retryNum"`
ErrorMailNotify bool `json:"errorMailNotify"`
ErrorAPINotify bool `json:"errorAPINotify"`
ErrorDingdingNotify bool `json:"errorDingdingNotify"`
Addr string `json:"addr" rule:"required,请填写addr"`
JobID uint `json:"jobID"`
Name string `json:"name" rule:"required,请填写name"`
MailTo []string `json:"mailTo"`
APITo []string `json:"APITo"`
DingdingTo []string `json:"DingdingTo"`
Command []string `json:"command" rule:"required,请填写command"`
Code string `json:"code"`
WorkUser string `json:"workUser"`
WorkIp []string `json:"workIp"`
WorkEnv []string `json:"workEnv"`
WorkDir string `json:"workDir"`
FailRestart bool `json:"failRestart"`
RetryNum int `json:"retryNum"`
ErrorMailNotify bool `json:"errorMailNotify"`
ErrorAPINotify bool `json:"errorAPINotify"`
ErrorDingdingNotify bool `json:"errorDingdingNotify"`
}

func (p *EditDaemonJobReqParams) Verify(ctx *myctx) error {
Expand Down Expand Up @@ -315,6 +315,7 @@ type LoginReqParams struct {
Username string `json:"username" rule:"required,请输入用户名"`
Passwd string `json:"passwd" rule:"required,请输入密码"`
Remember bool `json:"remember"`
IsLdap bool `json:"is_ldap"`
}

func (p *LoginReqParams) Verify(ctx *myctx) error {
Expand Down Expand Up @@ -415,7 +416,7 @@ func (p *AuditJobReqParams) Verify(ctx *myctx) error {
return err
}

if jobTypeMap[p.JobType] == false {
if !jobTypeMap[p.JobType] {
return paramsError
}

Expand Down
16 changes: 13 additions & 3 deletions jiacrontab_admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,19 @@ func Login(ctx *myctx) {
ctx.respParamError(err)
return
}
if !user.Verify(reqBody.Username, reqBody.Passwd) {
ctx.respAuthFailed(errors.New("帐号或密码不正确"))
return

if reqBody.IsLdap {
luser, err := ctx.adm.ldap.Login(reqBody.Username, reqBody.Passwd)
if err != nil {
ctx.respAuthFailed(err)
return
}
user = *luser
} else {
if !user.Verify(reqBody.Username, reqBody.Passwd) {
ctx.respAuthFailed(errors.New("帐号或密码不正确"))
return
}
}

customerClaims.ExpiresAt = cfg.Jwt.Expires + time.Now().Unix()
Expand Down
2 changes: 1 addition & 1 deletion models/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func InitModel(driverName string, dsn string, debug bool) error {
}

func AutoMigrate() {
if err := DB().AutoMigrate(&Node{}, &Group{}, &User{}, &Event{}, &JobHistory{}); err != nil {
if err := DB().AutoMigrate(&SysSetting{}, &Node{}, &Group{}, &User{}, &Event{}, &JobHistory{}); err != nil {
log.Fatal(err)
}
if err := DB().FirstOrCreate(&SuperGroup).Error; err != nil {
Expand Down
13 changes: 13 additions & 0 deletions models/setting.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package models

import (
"encoding/json"

"gorm.io/gorm"
)

type SysSetting struct {
gorm.Model
Class int `json:"class"` // 设置分类,1 Ldap配置
Content json.RawMessage `json:"content" gorm:"column:content; type:json"` // 配置内容
}
Loading

0 comments on commit 8be0d47

Please sign in to comment.