Compare commits
4 commits
71805cf65c
...
e6bca3ac8f
| Author | SHA1 | Date | |
|---|---|---|---|
| e6bca3ac8f | |||
| f517be8afb | |||
| 3e6b95bf40 | |||
| 4a68d0700d |
7 changed files with 123 additions and 69 deletions
2
addy.go
2
addy.go
|
|
@ -115,6 +115,7 @@ func addyAliasAPI(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
err = conn.ServiceBind()
|
err = conn.ServiceBind()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -197,6 +198,7 @@ func addyAliasAPIDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
err = conn.ServiceBind()
|
err = conn.ServiceBind()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
72
change.go
72
change.go
|
|
@ -67,31 +67,51 @@ func changePassword(w http.ResponseWriter, r *http.Request) {
|
||||||
// Check the two new passwords are identical
|
// Check the two new passwords are identical
|
||||||
if r.PostFormValue("newpassword") != r.PostFormValue("new2password") {
|
if r.PostFormValue("newpassword") != r.PostFormValue("new2password") {
|
||||||
renderError(http.StatusNotAcceptable, "New passwords are not identical. Please retry.")
|
renderError(http.StatusNotAcceptable, "New passwords are not identical. Please retry.")
|
||||||
} else if len(r.PostFormValue("login")) == 0 {
|
return
|
||||||
renderError(http.StatusNotAcceptable, "Please provide a valid login")
|
|
||||||
} else if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil {
|
|
||||||
renderError(http.StatusNotAcceptable, "The password you chose doesn't respect all constraints: "+err.Error())
|
|
||||||
} else {
|
|
||||||
conn, err := myLDAP.Connect()
|
|
||||||
if err != nil || conn == nil {
|
|
||||||
log.Println(err)
|
|
||||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
|
||||||
} else if err := conn.ServiceBind(); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
|
||||||
} else if dn, err := conn.SearchDN(r.PostFormValue("login"), true); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
// User not found: perform a dummy bind to prevent username enumeration via timing.
|
|
||||||
conn.Bind("cn=dummy,"+myLDAP.BaseDN, r.PostFormValue("password"))
|
|
||||||
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
|
||||||
} else if err := conn.Bind(dn, r.PostFormValue("password")); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
|
||||||
} else if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
|
||||||
} else {
|
|
||||||
displayMsg(w, "Password successfully changed!", http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if len(r.PostFormValue("login")) == 0 {
|
||||||
|
renderError(http.StatusNotAcceptable, "Please provide a valid login")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil {
|
||||||
|
renderError(http.StatusNotAcceptable, "The password you chose doesn't respect all constraints: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := myLDAP.Connect()
|
||||||
|
if err != nil || conn == nil {
|
||||||
|
log.Println(err)
|
||||||
|
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
if err := conn.ServiceBind(); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dn, err := conn.SearchDN(r.PostFormValue("login"), true)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
// User not found: perform a dummy bind to prevent username enumeration via timing.
|
||||||
|
conn.Bind("cn=dummy,"+myLDAP.BaseDN, r.PostFormValue("password"))
|
||||||
|
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conn.Bind(dn, r.PostFormValue("password")); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
displayMsg(w, "Password successfully changed!", http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
84
ldap.go
84
ldap.go
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/amoghe/go-crypt"
|
"github.com/amoghe/go-crypt"
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
|
@ -19,38 +21,45 @@ type LDAP struct {
|
||||||
BaseDN string
|
BaseDN string
|
||||||
ServiceDN string
|
ServiceDN string
|
||||||
ServicePassword string
|
ServicePassword string
|
||||||
MailHost string
|
}
|
||||||
MailPort int
|
|
||||||
MailUser string
|
type SMTPConfig struct {
|
||||||
MailPassword string
|
MailHost string
|
||||||
MailFrom string
|
MailPort int
|
||||||
|
MailUser string
|
||||||
|
MailPassword string
|
||||||
|
MailFrom string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l LDAP) Connect() (*LDAPConn, error) {
|
func (l LDAP) Connect() (*LDAPConn, error) {
|
||||||
if l.Ssl {
|
addr := fmt.Sprintf("%s:%d", l.Host, l.Port)
|
||||||
if c, err := ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port), &tls.Config{ServerName: l.Host}); err != nil {
|
|
||||||
return nil, errors.New("unable to establish LDAPS connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error())
|
|
||||||
} else {
|
|
||||||
return &LDAPConn{
|
|
||||||
LDAP: l,
|
|
||||||
connection: c,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
} else if c, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", l.Host, l.Port)); err != nil {
|
|
||||||
return nil, errors.New("unable to establish LDAP connection to " + fmt.Sprintf("%s:%d", l.Host, l.Port) + ": " + err.Error())
|
|
||||||
} else {
|
|
||||||
if l.Starttls {
|
|
||||||
if err = c.StartTLS(&tls.Config{ServerName: l.Host}); err != nil {
|
|
||||||
c.Close()
|
|
||||||
return nil, errors.New("unable to StartTLS: " + err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &LDAPConn{
|
var opts []ldap.DialOpt
|
||||||
LDAP: l,
|
if l.Ssl {
|
||||||
connection: c,
|
opts = append(opts, ldap.DialWithTLSConfig(&tls.Config{ServerName: l.Host}))
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scheme := "ldap"
|
||||||
|
if l.Ssl {
|
||||||
|
scheme = "ldaps"
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := ldap.DialURL(fmt.Sprintf("%s://%s", scheme, addr), opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to establish %s connection to %s: %w", strings.ToUpper(scheme), addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.Starttls {
|
||||||
|
if err = c.StartTLS(&tls.Config{ServerName: l.Host}); err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, fmt.Errorf("unable to StartTLS: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LDAPConn{
|
||||||
|
LDAP: l,
|
||||||
|
connection: c,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type LDAPConn struct {
|
type LDAPConn struct {
|
||||||
|
|
@ -58,6 +67,10 @@ type LDAPConn struct {
|
||||||
connection *ldap.Conn
|
connection *ldap.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l LDAPConn) Close() {
|
||||||
|
l.connection.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func (l LDAPConn) ServiceBind() error {
|
func (l LDAPConn) ServiceBind() error {
|
||||||
return l.connection.Bind(l.ServiceDN, l.ServicePassword)
|
return l.connection.Bind(l.ServiceDN, l.ServicePassword)
|
||||||
}
|
}
|
||||||
|
|
@ -85,8 +98,11 @@ func (l LDAPConn) SearchDN(username string, person bool) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sr.Entries) != 1 {
|
if len(sr.Entries) == 0 {
|
||||||
return "", errors.New("User does not exist or too many entries returned")
|
return "", fmt.Errorf("user %q not found", username)
|
||||||
|
}
|
||||||
|
if len(sr.Entries) > 1 {
|
||||||
|
return "", fmt.Errorf("multiple entries (%d) found for user %q", len(sr.Entries), username)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sr.Entries[0].DN, nil
|
return sr.Entries[0].DN, nil
|
||||||
|
|
@ -104,8 +120,11 @@ func (l LDAPConn) GetEntry(dn string) ([]*ldap.EntryAttribute, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sr.Entries) != 1 {
|
if len(sr.Entries) == 0 {
|
||||||
return nil, errors.New("User does not exist or too many entries returned")
|
return nil, fmt.Errorf("entry not found for DN %q", dn)
|
||||||
|
}
|
||||||
|
if len(sr.Entries) > 1 {
|
||||||
|
return nil, fmt.Errorf("multiple entries (%d) found for DN %q", len(sr.Entries), dn)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sr.Entries[0].Attributes, nil
|
return sr.Entries[0].Attributes, nil
|
||||||
|
|
@ -133,6 +152,7 @@ func (l LDAPConn) ChangePassword(dn string, rawpassword string) error {
|
||||||
|
|
||||||
modify := ldap.NewModifyRequest(dn, nil)
|
modify := ldap.NewModifyRequest(dn, nil)
|
||||||
modify.Replace("userPassword", []string{"{CRYPT}" + hashedpasswd})
|
modify.Replace("userPassword", []string{"{CRYPT}" + hashedpasswd})
|
||||||
|
modify.Replace("shadowLastChange", []string{strconv.FormatInt(time.Now().Unix()/86400, 10)})
|
||||||
|
|
||||||
return l.connection.Modify(modify)
|
return l.connection.Modify(modify)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
login.go
1
login.go
|
|
@ -78,6 +78,7 @@ func login(login string, password string) ([]*ldap.EntryAttribute, error) {
|
||||||
if err != nil || conn == nil {
|
if err != nil || conn == nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
if err = conn.ServiceBind(); err != nil {
|
if err = conn.ServiceBind(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
7
lost.go
7
lost.go
|
|
@ -129,6 +129,7 @@ func lostPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]any{"error": "Unable to process your request. Please try again later."})
|
displayTmplError(w, http.StatusInternalServerError, "lost.html", map[string]any{"error": "Unable to process your request. Please try again later."})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
// Generate the token
|
// Generate the token
|
||||||
token, dn, err := lostPasswordToken(conn, r.PostFormValue("login"))
|
token, dn, err := lostPasswordToken(conn, r.PostFormValue("login"))
|
||||||
|
|
@ -166,14 +167,14 @@ func lostPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Send the email
|
// Send the email
|
||||||
m := gomail.NewMessage()
|
m := gomail.NewMessage()
|
||||||
m.SetHeader("From", myLDAP.MailFrom)
|
m.SetHeader("From", mySMTP.MailFrom)
|
||||||
m.SetHeader("To", email)
|
m.SetHeader("To", email)
|
||||||
m.SetHeader("Subject", "SSO nemunai.re: password recovery")
|
m.SetHeader("Subject", "SSO nemunai.re: password recovery")
|
||||||
m.SetBody("text/plain", "Hello "+cn+"!\n\nSomeone, and we hope it's you, requested to reset your account password. \nIn order to continue, go to:\n"+myPublicURL+"/reset?l="+r.PostFormValue("login")+"&t="+token+"\n\nThis link expires in 1 hour and can only be used once.\n\nBest regards,\n-- \nnemunai.re SSO")
|
m.SetBody("text/plain", "Hello "+cn+"!\n\nSomeone, and we hope it's you, requested to reset your account password. \nIn order to continue, go to:\n"+myPublicURL+"/reset?l="+r.PostFormValue("login")+"&t="+token+"\n\nThis link expires in 1 hour and can only be used once.\n\nBest regards,\n-- \nnemunai.re SSO")
|
||||||
|
|
||||||
var s gomail.Sender
|
var s gomail.Sender
|
||||||
if myLDAP.MailHost != "" {
|
if mySMTP.MailHost != "" {
|
||||||
d := gomail.NewDialer(myLDAP.MailHost, myLDAP.MailPort, myLDAP.MailUser, myLDAP.MailPassword)
|
d := gomail.NewDialer(mySMTP.MailHost, mySMTP.MailPort, mySMTP.MailUser, mySMTP.MailPassword)
|
||||||
s, err = d.Dial()
|
s, err = d.Dial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Unable to connect to email server: " + err.Error())
|
log.Println("Unable to connect to email server: " + err.Error())
|
||||||
|
|
|
||||||
31
main.go
31
main.go
|
|
@ -31,9 +31,12 @@ var dockerRegistrySecret string
|
||||||
var allowedAliasDomains []string
|
var allowedAliasDomains []string
|
||||||
|
|
||||||
var myLDAP = LDAP{
|
var myLDAP = LDAP{
|
||||||
Host: "localhost",
|
Host: "localhost",
|
||||||
Port: 389,
|
Port: 389,
|
||||||
BaseDN: "dc=example,dc=com",
|
BaseDN: "dc=example,dc=com",
|
||||||
|
}
|
||||||
|
|
||||||
|
var mySMTP = SMTPConfig{
|
||||||
MailPort: 587,
|
MailPort: 587,
|
||||||
MailFrom: "noreply@example.com",
|
MailFrom: "noreply@example.com",
|
||||||
}
|
}
|
||||||
|
|
@ -115,8 +118,13 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
} else if cnt, err := io.ReadAll(fd); err != nil {
|
} else if cnt, err := io.ReadAll(fd); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
} else if err := json.Unmarshal(cnt, &myLDAP); err != nil {
|
} else {
|
||||||
log.Fatal(err)
|
if err := json.Unmarshal(cnt, &myLDAP); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(cnt, &mySMTP); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -156,17 +164,17 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if val, ok := os.LookupEnv("SMTP_HOST"); ok {
|
if val, ok := os.LookupEnv("SMTP_HOST"); ok {
|
||||||
myLDAP.MailHost = val
|
mySMTP.MailHost = val
|
||||||
}
|
}
|
||||||
if val, ok := os.LookupEnv("SMTP_PORT"); ok {
|
if val, ok := os.LookupEnv("SMTP_PORT"); ok {
|
||||||
if port, err := strconv.Atoi(val); err == nil {
|
if port, err := strconv.Atoi(val); err == nil {
|
||||||
myLDAP.MailPort = port
|
mySMTP.MailPort = port
|
||||||
} else {
|
} else {
|
||||||
log.Println("Invalid value for SMTP_PORT:", val)
|
log.Println("Invalid value for SMTP_PORT:", val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if val, ok := os.LookupEnv("SMTP_USER"); ok {
|
if val, ok := os.LookupEnv("SMTP_USER"); ok {
|
||||||
myLDAP.MailUser = val
|
mySMTP.MailUser = val
|
||||||
}
|
}
|
||||||
if val, ok := os.LookupEnv("SMTP_PASSWORD_FILE"); ok {
|
if val, ok := os.LookupEnv("SMTP_PASSWORD_FILE"); ok {
|
||||||
if fd, err := os.Open(val); err != nil {
|
if fd, err := os.Open(val); err != nil {
|
||||||
|
|
@ -176,13 +184,13 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
} else {
|
} else {
|
||||||
fd.Close()
|
fd.Close()
|
||||||
myLDAP.MailPassword = string(cnt)
|
mySMTP.MailPassword = string(cnt)
|
||||||
}
|
}
|
||||||
} else if val, ok := os.LookupEnv("SMTP_PASSWORD"); ok {
|
} else if val, ok := os.LookupEnv("SMTP_PASSWORD"); ok {
|
||||||
myLDAP.MailPassword = val
|
mySMTP.MailPassword = val
|
||||||
}
|
}
|
||||||
if val, ok := os.LookupEnv("SMTP_FROM"); ok {
|
if val, ok := os.LookupEnv("SMTP_FROM"); ok {
|
||||||
myLDAP.MailFrom = val
|
mySMTP.MailFrom = val
|
||||||
}
|
}
|
||||||
if val, ok := os.LookupEnv("PUBLIC_URL"); ok {
|
if val, ok := os.LookupEnv("PUBLIC_URL"); ok {
|
||||||
myPublicURL = val
|
myPublicURL = val
|
||||||
|
|
@ -207,6 +215,7 @@ func main() {
|
||||||
if err != nil || conn == nil {
|
if err != nil || conn == nil {
|
||||||
log.Fatalf("Unable to connect to LDAP: %s", err.Error())
|
log.Fatalf("Unable to connect to LDAP: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
token, dn, err := lostPasswordToken(conn, login)
|
token, dn, err := lostPasswordToken(conn, login)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
1
reset.go
1
reset.go
|
|
@ -79,6 +79,7 @@ func resetPassword(w http.ResponseWriter, r *http.Request) {
|
||||||
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
// Bind as service to perform the password change
|
// Bind as service to perform the password change
|
||||||
err = conn.ServiceBind()
|
err = conn.ServiceBind()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue