Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev.auth ldap #406

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 34 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,52 @@ go 1.13

require (
github.com/360EntSecGroup-Skylar/excelize v1.4.1
github.com/AlecAivazis/survey/v2 v2.2.7
github.com/GoAdminGroup/html v0.0.1
github.com/GoAdminGroup/themes v0.0.41
github.com/NebulousLabs/fastrand v0.0.0-20181203155948-6fb6489aac4e
github.com/astaxie/beego v1.12.3
github.com/buaazp/fasthttprouter v0.1.1
github.com/denisenkom/go-mssqldb v0.0.0-20200206145737-bbfc9a55622e
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect
github.com/gavv/httpexpect v2.0.0+incompatible
github.com/gin-gonic/gin v1.6.3
github.com/go-chi/chi v1.5.1
github.com/go-ldap/ldap/v3 v3.2.4
github.com/go-sql-driver/mysql v1.5.0
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
github.com/gobuffalo/buffalo v0.16.21
github.com/gogf/gf v1.15.1
github.com/gorilla/mux v1.7.4
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/jawher/mow.cli v1.2.0
github.com/jteeuwen/go-bindata v3.0.7+incompatible
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect
github.com/kataras/iris/v12 v12.1.8
github.com/labstack/echo/v4 v4.1.17
github.com/lib/pq v1.3.0
github.com/magiconair/properties v1.8.1
github.com/mattn/go-colorable v0.1.4 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b
github.com/moul/http2curl v1.0.0 // indirect
github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/satori/go.uuid v1.2.0
github.com/sirupsen/logrus v1.4.2
github.com/stretchr/objx v0.2.0 // indirect
github.com/schollz/progressbar v1.0.0
github.com/sclevine/agouti v3.0.0+incompatible
github.com/stretchr/testify v1.5.1
github.com/tdewolff/minify v2.3.6+incompatible
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/test v1.0.6 // indirect
github.com/valyala/fasthttp v1.19.0
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
github.com/yudai/gojsondiff v1.0.0 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
go.uber.org/zap v1.14.1
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d
golang.org/x/sys v0.0.0-20200219091948-cb0a6d8edb6c // indirect
golang.org/x/text v0.3.2
google.golang.org/appengine v1.6.5 // indirect
gopkg.in/ini.v1 v1.51.0
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
golang.org/x/text v0.3.3
gopkg.in/ini.v1 v1.51.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.2.8
xorm.io/xorm v1.0.2
)
1,481 changes: 1,468 additions & 13 deletions go.sum

Large diffs are not rendered by default.

81 changes: 43 additions & 38 deletions modules/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package auth

import (
"net/http"
"strings"
"sync"

"github.com/GoAdminGroup/go-admin/modules/db/dialect"
Expand All @@ -18,30 +20,55 @@ import (
"golang.org/x/crypto/bcrypt"
)

// Auth get the user model from Context.
func Auth(ctx *context.Context) models.UserModel {
return ctx.User().(models.UserModel)
type Method string

const (
GeneralMethod = "0"
LdapMethod = "1"
)

type Authenticator interface {
Authenticate(req *http.Request) (models.UserModel, error)
}

// Check check the password and username and return the user model.
func Check(password string, username string, conn db.Connection) (user models.UserModel, ok bool) {
type generalAuth struct {
conn db.Connection
}

user = models.User().SetConn(conn).FindByUserName(username)
func (auth *generalAuth) Authenticate(req *http.Request) (user models.UserModel, err error) {
password := req.FormValue("password")
username := req.FormValue("username")

if user.IsEmpty() {
ok = false
} else {
if comparePassword(password, user.Password) {
ok = true
user = user.WithRoles().WithPermissions().WithMenus()
user.UpdatePwd(EncodePassword([]byte(password)))
} else {
ok = false
}
if strings.TrimSpace(username) == "" {
err = ErrUserInvalidName
return
}
if strings.TrimSpace(password) == "" {
err = ErrUserInvalidPassword
return
}
account := models.NewGeneralAccount().WithConn(auth.conn).FindByUsername(username)
if account.IsEmpty() {
err = ErrGeneralAccountNotFound
return
}
if !comparePassword(password, account.Password) {
err = ErrGeneralAccountIncorrectPassword
return
}
user = models.User().SetConn(auth.conn).Find(account.UserId)
return
}

func NewGeneralAuth(conn db.Connection) Authenticator {
return &generalAuth{conn: conn}
}

// Auth get the user model from Context.
func Auth(ctx *context.Context) models.UserModel {
return ctx.User().(models.UserModel)
}

func comparePassword(comPwd, pwdHash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(pwdHash), []byte(comPwd))
return err == nil
Expand All @@ -56,28 +83,6 @@ func EncodePassword(pwd []byte) string {
return string(hash)
}

// SetCookie set the cookie.
func SetCookie(ctx *context.Context, user models.UserModel, conn db.Connection) error {
ses, err := InitSession(ctx, conn)

if err != nil {
return err
}

return ses.Add("user_id", user.Id)
}

// DelCookie delete the cookie from Context.
func DelCookie(ctx *context.Context, conn db.Connection) error {
ses, err := InitSession(ctx, conn)

if err != nil {
return err
}

return ses.Clear()
}

type TokenService struct {
tokens CSRFToken
lock sync.Mutex
Expand Down
36 changes: 36 additions & 0 deletions modules/auth/cookie.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package auth

import (
"github.com/GoAdminGroup/go-admin/context"
"github.com/GoAdminGroup/go-admin/modules/db"
"github.com/GoAdminGroup/go-admin/plugins/admin/models"
)

type CookieManager interface {
SetCookie(ctx *context.Context, user models.UserModel) error
DelCookie(ctx *context.Context) error
}

type cookieManager struct {
conn db.Connection
}

func (c *cookieManager) SetCookie(ctx *context.Context, user models.UserModel) error {
ses, err := InitSession(ctx, c.conn)
if err != nil {
return err
}
return ses.Add("user_id", user.Id)
}

func (c *cookieManager) DelCookie(ctx *context.Context) error {
ses, err := InitSession(ctx, c.conn)
if err != nil {
return err
}
return ses.Clear()
}

func NewCookieManger(conn db.Connection) CookieManager {
return &cookieManager{conn: conn}
}
17 changes: 17 additions & 0 deletions modules/auth/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package auth

import "errors"

var (
ErrGeneralAccountNotFound = errors.New("general account: not found")
ErrGeneralAccountIncorrectPassword = errors.New("general account: incorrect password")

ErrLdapInvalidConfig = errors.New("ldap: invalid config")
ErrLdapInvalidUrlSchema = errors.New("ldap: invalid server url schema")
ErrLdapNameNotFound = errors.New("ldap account: name not found")
ErrLdapIncorrectPassword = errors.New("ldap account: incorrect password")

ErrUserNotFound = errors.New("user: not found")
ErrUserInvalidName = errors.New("user: invalid name")
ErrUserInvalidPassword = errors.New("user: invalid password")
)
97 changes: 97 additions & 0 deletions modules/auth/ldap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package auth

import (
"crypto/tls"
"fmt"
"github.com/GoAdminGroup/go-admin/modules/db"
"github.com/GoAdminGroup/go-admin/plugins/admin/models"
"github.com/go-ldap/ldap/v3"
"net/http"
"net/url"
)

type LdapConfig struct {
urls []string
bindDN string
BindPwd string
baseDN string
}

func NewLdapConfig(urls []string, bindDN, bindPwd, baseDN string) *LdapConfig {
return &LdapConfig{urls: urls, bindDN: bindDN, BindPwd: bindPwd, baseDN: baseDN}
}

type ldapAuth struct {
ldapConf *LdapConfig
dbConn db.Connection
}

func NewLdapAuth(dbConn db.Connection, conf *LdapConfig) Authenticator {
return &ldapAuth{dbConn: dbConn, ldapConf: conf}
}

func (auth *ldapAuth) Authenticate(req *http.Request) (user models.UserModel, err error) {
var (
username = req.FormValue("username")
password = req.FormValue("password")
ldapConn *ldap.Conn
result *ldap.SearchResult
)
defer user.ReleaseConn()

if ldapConn, err = ConnectLdap(auth.ldapConf.urls); err != nil {
return
}
defer ldapConn.Close()

if _, err = ldapConn.SimpleBind(ldap.NewSimpleBindRequest(auth.ldapConf.bindDN, auth.ldapConf.BindPwd, nil)); err != nil {
return
}
searchReq := ldap.NewSearchRequest(auth.ldapConf.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(sAMAccountName=%s))", username),
[]string{"dn", "mail"}, nil)

if result, err = ldapConn.Search(searchReq); err != nil {
return
}
if len(result.Entries) == 0 {
err = ErrLdapNameNotFound
return
}
for _, entry := range result.Entries {
if err = ldapConn.Bind(entry.DN, password); err == nil {
ldapAccount := models.NewLdapAccount().WithConn(auth.dbConn).FindByUsernameAndDN(username, entry.DN)
if ldapAccount.IsEmpty() {
_, user, err = models.NewLdapAccount().WithConn(auth.dbConn).CreateUser(username, entry.DN)
return
}
if user = models.User().SetConn(auth.dbConn).Find(ldapAccount.UserId); user.IsEmpty() {
err = ErrUserNotFound
}
return
}
}
err = ErrLdapIncorrectPassword
return
}

func ConnectLdap(urls []string) (*ldap.Conn, error) {
for _, addr := range urls {
parsedURL, err := url.Parse(addr)
if err != nil {
return nil, err
}
switch parsedURL.Scheme {
case "ldap":
return ldap.Dial("tcp", parsedURL.Host)
case "ldaps":
tlsConf := tls.Config{
ServerName: parsedURL.Hostname(),
}
return ldap.DialTLS("tcp", parsedURL.Host, &tlsConf)
default:
return nil, ErrLdapInvalidUrlSchema
}
}
return nil, ErrLdapInvalidConfig
}
2 changes: 2 additions & 0 deletions modules/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ type Config struct {
// file connection.go.
Databases DatabaseList `json:"database,omitempty" yaml:"database,omitempty" ini:"database,omitempty"`

Ldap Ldap `json:"ldap,omitempty" yaml:"ldap,omitempty" ini:"ldap,omitempty"`

// The application unique ID. Once generated, don't modify.
AppID string `json:"app_id,omitempty" yaml:"app_id,omitempty" ini:"app_id,omitempty"`

Expand Down
6 changes: 6 additions & 0 deletions modules/config/config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
---
ldap:
enable: false
server_urls: []
bind_dn:
bind_pwd:
base_dn:
database:
default:
host: 127.0.0.1
Expand Down
9 changes: 9 additions & 0 deletions modules/config/ldap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package config

type Ldap struct {
Enable bool `json:"enable,omitempty" yaml:"enable,omitempty" ini:"enable,omitempty"`
ServerUrls []string `json:"server_urls,omitempty" yaml:"server_urls,omitempty" ini:"server_urls,omitempty"`
BindDN string `json:"bind_dn,omitempty" yaml:"bind_dn,omitempty" ini:"bind_dn,omitempty"`
BindPwd string `json:"bind_pwd,omitempty" yaml:"bind_pwd,omitempty" ini:"bind_pwd,omitempty"`
BaseDN string `json:"base_dn,omitempty" yaml:"base_dn,omitempty" ini:"base_dn,omitempty"`
}
3 changes: 3 additions & 0 deletions modules/language/cn.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ var cn = LangSet{
"login": "登录",
"login fail": "登录失败",

"user ldap": "LDAP用户",
"user general": "普通用户",

"admin": "管理",
"user": "用户",
"users": "用户",
Expand Down
Loading