This is an automated email from the ASF dual-hosted git repository.

rob pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git

commit b7c3c2e8158ca45f862980d4c59525e76e43cf27
Author: Dylan Volz <dylan_v...@comcast.com>
AuthorDate: Tue Mar 13 13:37:28 2018 -0600

    implement login flow in go for local user and ldap
---
 traffic_ops/traffic_ops_golang/auth/ldap.go        |  64 ++++++++++++
 traffic_ops/traffic_ops_golang/auth/login.go       | 112 +++++++++++++++++++++
 traffic_ops/traffic_ops_golang/config/config.go    |  40 +++++++-
 traffic_ops/traffic_ops_golang/routes.go           |   3 +
 .../traffic_ops_golang/traffic_ops_golang.go       |   3 +-
 5 files changed, 220 insertions(+), 2 deletions(-)

diff --git a/traffic_ops/traffic_ops_golang/auth/ldap.go 
b/traffic_ops/traffic_ops_golang/auth/ldap.go
new file mode 100644
index 0000000..1903505
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/auth/ldap.go
@@ -0,0 +1,64 @@
+package auth
+
+import (
+       "crypto/tls"
+       "errors"
+       "fmt"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-log"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/config"
+
+       "gopkg.in/ldap.v2"
+)
+
+func LookupUserDN(username string, cfg *config.ConfigLDAP) (string, bool, 
error) {
+       l, err := ldap.DialTLS("tcp", cfg.Host, &tls.Config{InsecureSkipVerify: 
true})
+       if err != nil {
+               log.Errorln("error dialing tls")
+               return "", false, err
+       }
+       defer l.Close()
+       // Bind with admin user
+       err = l.Bind(cfg.AdminDN, cfg.AdminPass)
+       if err != nil {
+               log.Errorln("error binding admin user")
+               return "", false, err
+       }
+
+       // Search for the given username
+       searchRequest := ldap.NewSearchRequest(
+               cfg.SearchBase,
+               ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+               
fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(sAMAccountName=%s))", 
username),
+               []string{"dn"},
+               nil,
+       )
+
+       sr, err := l.Search(searchRequest)
+       if err != nil {
+               log.Errorln("error issuing search:")
+               return "", false, err
+       }
+
+       if len(sr.Entries) != 1 {
+               return "", false, errors.New("User does not exist or too many 
entries returned")
+       }
+       userDN := sr.Entries[0].DN
+       return userDN, true, nil
+}
+
+func AuthenticateUserDN(userDN string, password string, cfg 
*config.ConfigLDAP) (bool, error) {
+       l, err := ldap.DialTLS("tcp", cfg.Host, &tls.Config{InsecureSkipVerify: 
true})
+       if err != nil {
+               log.Errorln("error dialing tls")
+               return false, err
+       }
+       defer l.Close()
+
+       // Bind as the user to verify their password
+       err = l.Bind(userDN, password)
+       if err != nil {
+               return false, err
+       }
+       return true, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/auth/login.go 
b/traffic_ops/traffic_ops_golang/auth/login.go
new file mode 100644
index 0000000..e55a6d8
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/auth/login.go
@@ -0,0 +1,112 @@
+package auth
+
+import (
+       "crypto/sha1"
+       "encoding/hex"
+       "encoding/json"
+       "fmt"
+       "net/http"
+
+       "github.com/jmoiron/sqlx"
+
+       "time"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-log"
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/config"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tocookie"
+       "github.com/pkg/errors"
+)
+
+type passwordForm struct {
+       Username string `json:"u"`
+       Password string `json:"p"`
+}
+
+func LoginHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
+       return func(w http.ResponseWriter, r *http.Request) {
+               handleErrs := tc.GetHandleErrorsFunc(w, r)
+               defer r.Body.Close()
+               form := passwordForm{}
+               if err := json.NewDecoder(r.Body).Decode(&form); err != nil {
+                       handleErrs(http.StatusBadRequest, err)
+                       return
+               }
+               authenticated, err := checkLocalUser(form, db)
+               if err != nil {
+                       log.Errorf("error checking local user: %s\n", 
err.Error())
+               }
+               var ldapErr error
+               if !authenticated {
+                       if cfg.LDAPEnabled {
+                               authenticated, ldapErr = checkLDAPUser(form, 
cfg.ConfigLDAP)
+                               if ldapErr != nil {
+                                       log.Errorf("error checking ldap user: 
%s\n", ldapErr.Error())
+                               }
+                       }
+               }
+               resp := struct {
+                       tc.Alerts
+               }{}
+               if authenticated {
+                       expiry := time.Now().Add(time.Hour * 6)
+                       cookie := tocookie.New(form.Username, expiry, 
cfg.Secrets[0])
+                       httpCookie := http.Cookie{Name: "mojolicious", Value: 
cookie, Path: "/", Expires: expiry, HttpOnly: true}
+                       http.SetCookie(w, &httpCookie)
+                       resp = struct {
+                               tc.Alerts
+                       }{tc.CreateAlerts(tc.SuccessLevel, "Successfully logged 
in.")}
+
+               } else {
+                       resp = struct {
+                               tc.Alerts
+                       }{tc.CreateAlerts(tc.ErrorLevel, "Invalid username or 
password.")}
+               }
+               respBts, err := json.Marshal(resp)
+               if err != nil {
+                       handleErrs(http.StatusInternalServerError, err)
+                       return
+               }
+
+               w.Header().Set(tc.ContentType, tc.ApplicationJson)
+               fmt.Fprintf(w, "%s", respBts)
+       }
+}
+
+func checkLocalUser(form passwordForm, db *sqlx.DB) (bool, error) {
+       var hashedPassword string
+       err := db.Get(&hashedPassword, "SELECT local_passwd FROM tm_user WHERE 
username=$1", form.Username)
+       if err != nil {
+               return false, err
+       }
+       err = VerifyPassword(form.Password, hashedPassword)
+       if err != nil {
+               if hashedPassword == sha1Hex(form.Password) {
+                       return true, nil
+               }
+               return false, err
+       }
+       return true, nil
+}
+
+func sha1Hex(s string) string {
+       // SHA1 hash
+       hash := sha1.New()
+       hash.Write([]byte(s))
+       hashBytes := hash.Sum(nil)
+
+       // Hexadecimal conversion
+       hexSha1 := hex.EncodeToString(hashBytes)
+       return hexSha1
+}
+
+func checkLDAPUser(form passwordForm, cfg *config.ConfigLDAP) (bool, error) {
+       userDN, valid, err := LookupUserDN(form.Username, cfg)
+       if err != nil {
+               return false, err
+       }
+       if valid {
+               return AuthenticateUserDN(userDN, form.Password, cfg)
+       }
+       return false, errors.New("User not found in LDAP")
+}
diff --git a/traffic_ops/traffic_ops_golang/config/config.go 
b/traffic_ops/traffic_ops_golang/config/config.go
index f6d0f10..4864845 100644
--- a/traffic_ops/traffic_ops_golang/config/config.go
+++ b/traffic_ops/traffic_ops_golang/config/config.go
@@ -42,6 +42,8 @@ type Config struct {
        // NOTE: don't care about any other fields for now..
        RiakAuthOptions *riak.AuthOptions
        RiakEnabled     bool
+       ConfigLDAP      *ConfigLDAP
+       LDAPEnabled     bool
        Version         string
 }
 
@@ -84,6 +86,13 @@ type ConfigDatabase struct {
        SSL         bool   `json:"ssl"`
 }
 
+type ConfigLDAP struct {
+       AdminPass  string `json:"admin_pass"`
+       SearchBase string `json:"search_base"`
+       AdminDN    string `json:"admin_dn"`
+       Host       string `json:"host"`
+}
+
 // ErrorLog - critical messages
 func (c Config) ErrorLog() log.LogLocation {
        return log.LogLocation(c.LogLocationError)
@@ -108,7 +117,7 @@ func (c Config) EventLog() log.LogLocation {
 }
 
 // LoadConfig - reads the config file into the Config struct
-func LoadConfig(cdnConfPath string, dbConfPath string, riakConfPath string, 
appVersion string) (Config, error) {
+func LoadConfig(cdnConfPath string, dbConfPath string, riakConfPath string, 
ldapConfPath string, appVersion string) (Config, error) {
        // load json from cdn.conf
        confBytes, err := ioutil.ReadFile(cdnConfPath)
        if err != nil {
@@ -142,6 +151,16 @@ func LoadConfig(cdnConfPath string, dbConfPath string, 
riakConfPath string, appV
                }
        }
 
+       if ldapConfPath != "" {
+               cfg.LDAPEnabled, cfg.ConfigLDAP, err = 
GetLDAPConfig(ldapConfPath)
+               if err != nil {
+                       cfg.LDAPEnabled = false // probably unnecessary
+                       return cfg, fmt.Errorf("parsing ldap config '%s': %v", 
ldapConfPath, err)
+               }
+       } else {
+               cfg.LDAPEnabled = false
+       }
+
        return cfg, err
 }
 
@@ -226,3 +245,22 @@ func ParseConfig(cfg Config) (Config, error) {
 
        return cfg, nil
 }
+
+func GetLDAPConfig(LDAPConfPath string) (bool, *ConfigLDAP, error) {
+       LDAPConfBytes, err := ioutil.ReadFile(LDAPConfPath)
+       if err != nil {
+
+               return false, nil, fmt.Errorf("reading LDAP conf '%v': %v", 
LDAPConfPath, err)
+       }
+       LDAPconf, err := getLDAPConf(string(LDAPConfBytes))
+       if err != nil {
+               return false, LDAPconf, fmt.Errorf("parsing LDAP conf '%v': 
%v", LDAPConfBytes, err)
+       }
+       return true, LDAPconf, nil
+}
+
+func getLDAPConf(s string) (*ConfigLDAP, error) {
+       ldapConf := ConfigLDAP{}
+       err := json.Unmarshal([]byte(s), &ldapConf)
+       return &ldapConf, err
+}
diff --git a/traffic_ops/traffic_ops_golang/routes.go 
b/traffic_ops/traffic_ops_golang/routes.go
index 3437de1..cba2190 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -119,6 +119,9 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
                //HWInfo
                {1.1, http.MethodGet, `hwinfo-wip/?(\.json)?$`, 
hwinfo.HWInfoHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
 
+               //Login
+               {1.2, http.MethodPost, `user/login/?$`, auth.LoginHandler(d.DB, 
d.Config), 0, NoAuth, nil}, {1.3, http.MethodPost, `user/login/?$`, 
auth.LoginHandler(d.DB, d.Config), 0, NoAuth, nil},
+
                //Parameter: CRUD
                {1.1, http.MethodGet, `parameters/?(\.json)?$`, 
api.ReadHandler(parameter.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
                {1.1, http.MethodGet, `parameters/{id}$`, 
api.ReadHandler(parameter.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go 
b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
index 9de1055..462a58a 100644
--- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
+++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
@@ -48,6 +48,7 @@ func main() {
        configFileName := flag.String("cfg", "", "The config file path")
        dbConfigFileName := flag.String("dbcfg", "", "The db config file path")
        riakConfigFileName := flag.String("riakcfg", "", "The riak config file 
path")
+       ldapConfigFileName := flag.String("ldapcfg", "", "the ldap config file 
path")
        flag.Parse()
 
        if *showVersion {
@@ -63,7 +64,7 @@ func main() {
        var err error
        var errorToLog error
 
-       if cfg, err = config.LoadConfig(*configFileName, *dbConfigFileName, 
*riakConfigFileName, version); err != nil {
+       if cfg, err = config.LoadConfig(*configFileName, *dbConfigFileName, 
*riakConfigFileName, *ldapConfigFileName, version); err != nil {
                if !strings.Contains(err.Error(), "riak conf") {
                        fmt.Println("Error loading config: " + err.Error())
                        return

-- 
To stop receiving notification emails like this one, please contact
r...@apache.org.

Reply via email to