mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-20 15:29:28 +01:00
Merge branch 'master' into develop
# Conflicts: # Dockerfile # api/login.go # api/projects/environment.go # api/projects/project.go # api/projects/templates.go # api/tasks/runner.go # api/users.go # db/versionHistory.go # util/bindata.go
This commit is contained in:
commit
db4948cb89
13
Dockerfile
13
Dockerfile
@ -1,12 +1,15 @@
|
|||||||
FROM alpine
|
FROM alpine:3.5
|
||||||
|
|
||||||
RUN apk add --no-cache git ansible mysql-client curl openssh-client
|
ENV SEMAPHORE_VERSION="2.2.0" SEMAPHORE_ARCH="linux_amd64"
|
||||||
RUN curl -L https://github.com/ansible-semaphore/semaphore/releases/download/v2.2.0/semaphore_linux_amd64 > /usr/bin/semaphore && chmod +x /usr/bin/semaphore && mkdir -p /etc/semaphore/playbooks
|
|
||||||
|
|
||||||
ADD ./scripts/docker-startup.sh /usr/bin/semaphore-startup.sh
|
RUN apk add --no-cache git ansible mysql-client curl openssh-client && \
|
||||||
RUN chmod +x /usr/bin/semaphore-startup.sh
|
curl -sSfL "https://github.com/ansible-semaphore/semaphore/releases/download/v$SEMAPHORE_VERSION/semaphore_$SEMAPHORE_ARCH" > /usr/bin/semaphore && \
|
||||||
|
chmod +x /usr/bin/semaphore && mkdir -p /etc/semaphore/playbooks
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
ADD semaphore-startup.sh /usr/bin/semaphore-startup.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/semaphore-startup.sh"]
|
ENTRYPOINT ["/usr/bin/semaphore-startup.sh"]
|
||||||
|
|
||||||
CMD ["/usr/bin/semaphore", "-config", "/etc/semaphore/semaphore_config.json"]
|
CMD ["/usr/bin/semaphore", "-config", "/etc/semaphore/semaphore_config.json"]
|
||||||
|
@ -53,6 +53,8 @@ definitions:
|
|||||||
created:
|
created:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
alert:
|
||||||
|
type: boolean
|
||||||
APIToken:
|
APIToken:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -75,6 +77,8 @@ definitions:
|
|||||||
created:
|
created:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
alert:
|
||||||
|
type: boolean
|
||||||
AccessKey:
|
AccessKey:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
121
api/login.go
121
api/login.go
@ -1,19 +1,105 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/ansible-semaphore/semaphore/db"
|
"github.com/ansible-semaphore/semaphore/db"
|
||||||
"github.com/ansible-semaphore/semaphore/util"
|
"github.com/ansible-semaphore/semaphore/util"
|
||||||
"github.com/castawaylabs/mulekick"
|
"github.com/castawaylabs/mulekick"
|
||||||
sq "github.com/masterminds/squirrel"
|
sq "github.com/masterminds/squirrel"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"gopkg.in/ldap.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ldapAuthentication(auth, password string) (error, models.User) {
|
||||||
|
|
||||||
|
if util.Config.LdapEnable != true {
|
||||||
|
return fmt.Errorf("LDAP not configured"), models.User{}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindusername := util.Config.LdapBindDN
|
||||||
|
bindpassword := util.Config.LdapBindPassword
|
||||||
|
|
||||||
|
l, err := ldap.Dial("tcp", util.Config.LdapServer)
|
||||||
|
if err != nil {
|
||||||
|
return err, models.User{}
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
|
||||||
|
// Reconnect with TLS if needed
|
||||||
|
if util.Config.LdapNeedTLS == true {
|
||||||
|
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
|
||||||
|
if err != nil {
|
||||||
|
return err, models.User{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First bind with a read only user
|
||||||
|
err = l.Bind(bindusername, bindpassword)
|
||||||
|
if err != nil {
|
||||||
|
return err, models.User{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search for the given username
|
||||||
|
searchRequest := ldap.NewSearchRequest(
|
||||||
|
util.Config.LdapSearchDN,
|
||||||
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
fmt.Sprintf(util.Config.LdapSearchFilter, auth),
|
||||||
|
[]string{util.Config.LdapMappings.DN},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
sr, err := l.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err, models.User{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sr.Entries) != 1 {
|
||||||
|
return fmt.Errorf("User does not exist or too many entries returned"), models.User{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind as the user to verify their password
|
||||||
|
userdn := sr.Entries[0].DN
|
||||||
|
err = l.Bind(userdn, password)
|
||||||
|
if err != nil {
|
||||||
|
return err, models.User{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user info and ensure authentication in case LDAP supports unauthenticated bind
|
||||||
|
searchRequest = ldap.NewSearchRequest(
|
||||||
|
util.Config.LdapSearchDN,
|
||||||
|
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
fmt.Sprintf(util.Config.LdapSearchFilter, auth),
|
||||||
|
[]string{util.Config.LdapMappings.DN, util.Config.LdapMappings.Mail, util.Config.LdapMappings.Uid, util.Config.LdapMappings.CN},
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
sr, err = l.Search(searchRequest)
|
||||||
|
if err != nil {
|
||||||
|
return err, models.User{}
|
||||||
|
}
|
||||||
|
|
||||||
|
ldapUser := models.User{
|
||||||
|
Username: sr.Entries[0].GetAttributeValue(util.Config.LdapMappings.Uid),
|
||||||
|
Created: time.Now(),
|
||||||
|
Name: sr.Entries[0].GetAttributeValue(util.Config.LdapMappings.CN),
|
||||||
|
Email: sr.Entries[0].GetAttributeValue(util.Config.LdapMappings.Mail),
|
||||||
|
External: true,
|
||||||
|
Alert: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("User " + ldapUser.Name + " with email " + ldapUser.Email + " authorized via LDAP correctly")
|
||||||
|
return nil, ldapUser
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func login(w http.ResponseWriter, r *http.Request) {
|
func login(w http.ResponseWriter, r *http.Request) {
|
||||||
var login struct {
|
var login struct {
|
||||||
Auth string `json:"auth" binding:"required"`
|
Auth string `json:"auth" binding:"required"`
|
||||||
@ -26,8 +112,18 @@ func login(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
login.Auth = strings.ToLower(login.Auth)
|
login.Auth = strings.ToLower(login.Auth)
|
||||||
|
|
||||||
q := sq.Select("*").From("user")
|
ldapErr, ldapUser := ldapAuthentication(login.Auth, login.Password)
|
||||||
|
|
||||||
|
if util.Config.LdapEnable == true && ldapErr != nil {
|
||||||
|
log.Info(ldapErr.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
q := sq.Select("*").
|
||||||
|
From("user")
|
||||||
|
|
||||||
|
var user models.User
|
||||||
|
if ldapErr != nil {
|
||||||
|
// Perform normal authorization
|
||||||
_, err := mail.ParseAddress(login.Auth)
|
_, err := mail.ParseAddress(login.Auth)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
q = q.Where("email=?", login.Auth)
|
q = q.Where("email=?", login.Auth)
|
||||||
@ -37,10 +133,9 @@ func login(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
query, args, _ := q.ToSql()
|
query, args, _ := q.ToSql()
|
||||||
|
|
||||||
var user db.User
|
|
||||||
if err := db.Mysql.SelectOne(&user, query, args...); err != nil {
|
if err := db.Mysql.SelectOne(&user, query, args...); err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
c.AbortWithStatus(400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,9 +143,29 @@ func login(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(login.Password)); err != nil {
|
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(login.Password)); err != nil {
|
||||||
|
c.AbortWithStatus(400)
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Check if that user already exist in database
|
||||||
|
q = q.Where("username=? and external=true", ldapUser.Username)
|
||||||
|
|
||||||
|
query, args, _ := q.ToSql()
|
||||||
|
|
||||||
|
if err := database.Mysql.SelectOne(&user, query, args...); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
//Create new user
|
||||||
|
user = ldapUser
|
||||||
|
if err := database.Mysql.Insert(&user); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
session := db.Session{
|
session := db.Session{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
|
@ -49,7 +49,8 @@ func GetEnvironment(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
q := squirrel.Select("*").
|
q := squirrel.Select("*").
|
||||||
From("project__environment pe")
|
From("project__environment pe").
|
||||||
|
Where("project_id=?", project.ID)
|
||||||
|
|
||||||
switch sort {
|
switch sort {
|
||||||
case "name":
|
case "name":
|
||||||
@ -76,6 +77,14 @@ func UpdateEnvironment(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var js map[string]interface{}
|
||||||
|
if json.Unmarshal([]byte(env.JSON), &js) != nil {
|
||||||
|
c.JSON(400, map[string]string{
|
||||||
|
"error": "JSON is not valid",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := db.Mysql.Exec("update project__environment set name=?, json=? where id=?", env.Name, env.JSON, oldEnv.ID); err != nil {
|
if _, err := db.Mysql.Exec("update project__environment set name=?, json=? where id=?", env.Name, env.JSON, oldEnv.ID); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
@ -87,7 +96,15 @@ func AddEnvironment(w http.ResponseWriter, r *http.Request) {
|
|||||||
project := context.Get(r, "project").(db.Project)
|
project := context.Get(r, "project").(db.Project)
|
||||||
var env db.Environment
|
var env db.Environment
|
||||||
|
|
||||||
if err := mulekick.Bind(w, r, &env); err != nil {
|
if err := c.Bind(&env); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var js map[string]interface{}
|
||||||
|
if json.Unmarshal([]byte(env.JSON), &js) != nil {
|
||||||
|
c.JSON(400, map[string]string{
|
||||||
|
"error": "JSON is not valid",
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,13 +62,14 @@ func UpdateProject(w http.ResponseWriter, r *http.Request) {
|
|||||||
project := context.Get(r, "project").(db.Project)
|
project := context.Get(r, "project").(db.Project)
|
||||||
var body struct {
|
var body struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
Alert bool `json:"alert"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mulekick.Bind(w, r, &body); err != nil {
|
if err := mulekick.Bind(w, r, &body); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := db.Mysql.Exec("update project set name=? where id=?", body.Name, project.ID); err != nil {
|
if _, err := db.Mysql.Exec("update project set name=?, alert=? where id=?", body.Name, body.Alert, project.ID); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +131,10 @@ func UpdateTemplate(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *template.Arguments == "" {
|
||||||
|
template.Arguments = nil
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := db.Mysql.Exec("update project__template set ssh_key_id=?, inventory_id=?, repository_id=?, environment_id=?, alias=?, playbook=?, arguments=?, override_args=? where id=?", template.SshKeyID, template.InventoryID, template.RepositoryID, template.EnvironmentID, template.Alias, template.Playbook, template.Arguments, template.OverrideArguments, oldTemplate.ID); err != nil {
|
if _, err := db.Mysql.Exec("update project__template set ssh_key_id=?, inventory_id=?, repository_id=?, environment_id=?, alias=?, playbook=?, arguments=?, override_args=? where id=?", template.SshKeyID, template.InventoryID, template.RepositoryID, template.EnvironmentID, template.Alias, template.Playbook, template.Arguments, template.OverrideArguments, oldTemplate.ID); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
106
api/tasks/alert.go
Normal file
106
api/tasks/alert.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package tasks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/ansible-semaphore/semaphore/models"
|
||||||
|
"github.com/ansible-semaphore/semaphore/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const emailTemplate = `Subject: Task '{{ .Alias }}' failed
|
||||||
|
|
||||||
|
Task {{ .TaskId }} with template '{{ .Alias }}' has failed!
|
||||||
|
Task log: <a href='{{ .TaskUrl }}'>{{ .TaskUrl }}</a>`
|
||||||
|
|
||||||
|
const telegramTemplate = `{"chat_id": "{{ .ChatId }}","text":"<b>Task {{ .TaskId }} with template '{{ .Alias }}' has failed!</b>\nTask log: <a href='{{ .TaskUrl }}'>{{ .TaskUrl }}</a>","parse_mode":"HTML"}`
|
||||||
|
|
||||||
|
type Alert struct {
|
||||||
|
TaskId string
|
||||||
|
Alias string
|
||||||
|
TaskUrl string
|
||||||
|
ChatId string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *task) sendMailAlert() {
|
||||||
|
|
||||||
|
if util.Config.EmailAlert != true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.alert != true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mailHost := util.Config.EmailHost + ":" + util.Config.EmailPort
|
||||||
|
|
||||||
|
var mailBuffer bytes.Buffer
|
||||||
|
alert := Alert{TaskId: strconv.Itoa(t.task.ID), Alias: t.template.Alias, TaskUrl: util.Config.WebHost + "/project/" + strconv.Itoa(t.template.ProjectID)}
|
||||||
|
tpl := template.New("mail body template")
|
||||||
|
tpl, err := tpl.Parse(emailTemplate)
|
||||||
|
err = tpl.Execute(&mailBuffer, alert)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.log("Can't generate alert template!")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, user := range t.users {
|
||||||
|
|
||||||
|
userObj, err := models.FetchUser(user)
|
||||||
|
|
||||||
|
if userObj.Alert != true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.log("Can't find user Email!")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.log("Sending email to " + userObj.Email + " from " + util.Config.EmailSender)
|
||||||
|
err = util.SendMail(mailHost, util.Config.EmailSender, userObj.Email, mailBuffer)
|
||||||
|
if err != nil {
|
||||||
|
t.log("Can't send email!")
|
||||||
|
t.log("Error: " + err.Error())
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *task) sendTelegramAlert() {
|
||||||
|
|
||||||
|
if util.Config.TelegramAlert != true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.alert != true {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var telegramBuffer bytes.Buffer
|
||||||
|
alert := Alert{TaskId: strconv.Itoa(t.task.ID), Alias: t.template.Alias, TaskUrl: util.Config.WebHost + "/project/" + strconv.Itoa(t.template.ProjectID), ChatId: util.Config.TelegramChat}
|
||||||
|
tpl := template.New("telegram body template")
|
||||||
|
tpl, err := tpl.Parse(telegramTemplate)
|
||||||
|
err = tpl.Execute(&telegramBuffer, alert)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.log("Can't generate alert template!")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Post("https://api.telegram.org/bot"+util.Config.TelegramToken+"/sendMessage", "application/json", &telegramBuffer)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.log("Can't send telegram alert!")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
t.log("Can't send telegram alert! Response code not 200!")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -25,11 +25,14 @@ type task struct {
|
|||||||
environment db.Environment
|
environment db.Environment
|
||||||
users []int
|
users []int
|
||||||
projectID int
|
projectID int
|
||||||
|
alert bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *task) fail() {
|
func (t *task) fail() {
|
||||||
t.task.Status = "error"
|
t.task.Status = "error"
|
||||||
t.updateStatus()
|
t.updateStatus()
|
||||||
|
t.sendMailAlert()
|
||||||
|
t.sendTelegramAlert()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *task) run() {
|
func (t *task) run() {
|
||||||
@ -44,7 +47,7 @@ func (t *task) run() {
|
|||||||
t.updateStatus()
|
t.updateStatus()
|
||||||
|
|
||||||
objType := "task"
|
objType := "task"
|
||||||
desc := "Task ID " + strconv.Itoa(t.task.ID) + " finished"
|
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Alias + ")" + " finished - " + strings.ToUpper(t.task.Status)
|
||||||
if err := (db.Event{
|
if err := (db.Event{
|
||||||
ProjectID: &t.projectID,
|
ProjectID: &t.projectID,
|
||||||
ObjectType: &objType,
|
ObjectType: &objType,
|
||||||
@ -72,7 +75,7 @@ func (t *task) run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
objType := "task"
|
objType := "task"
|
||||||
desc := "Task ID " + strconv.Itoa(t.task.ID) + " is running"
|
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Alias + ")" + " is running"
|
||||||
if err := (db.Event{
|
if err := (db.Event{
|
||||||
ProjectID: &t.projectID,
|
ProjectID: &t.projectID,
|
||||||
ObjectType: &objType,
|
ObjectType: &objType,
|
||||||
@ -143,6 +146,11 @@ func (t *task) populateDetails() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//get project alert setting
|
||||||
|
if err := t.fetch("Alert setting not found!", &t.alert, "select alert from project where id=?", t.template.ProjectID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// get project users
|
// get project users
|
||||||
var users []struct {
|
var users []struct {
|
||||||
ID int `db:"id"`
|
ID int `db:"id"`
|
||||||
@ -302,6 +310,13 @@ func (t *task) runPlaybook() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(t.environment.JSON) > 0 {
|
if len(t.environment.JSON) > 0 {
|
||||||
|
var js map[string]interface{}
|
||||||
|
err := json.Unmarshal([]byte(*t.template.JSON), &js)
|
||||||
|
if err != nil {
|
||||||
|
t.log("JSON is not valid")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
args = append(args, "--extra-vars", t.environment.JSON)
|
args = append(args, "--extra-vars", t.environment.JSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
18
api/users.go
18
api/users.go
@ -5,6 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
"github.com/ansible-semaphore/semaphore/db"
|
"github.com/ansible-semaphore/semaphore/db"
|
||||||
"github.com/ansible-semaphore/semaphore/util"
|
"github.com/ansible-semaphore/semaphore/util"
|
||||||
"github.com/castawaylabs/mulekick"
|
"github.com/castawaylabs/mulekick"
|
||||||
@ -58,12 +59,19 @@ func getUserMiddleware(w http.ResponseWriter, r *http.Request) {
|
|||||||
func updateUser(w http.ResponseWriter, r *http.Request) {
|
func updateUser(w http.ResponseWriter, r *http.Request) {
|
||||||
oldUser := context.Get(r, "_user").(db.User)
|
oldUser := context.Get(r, "_user").(db.User)
|
||||||
|
|
||||||
var user db.User
|
var user models.User
|
||||||
|
if err := c.Bind(&user); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if oldUser.External == true && oldUser.Username != user.Username {
|
||||||
|
log.Warn("Username is not editable for external LDAP users")
|
||||||
|
c.AbortWithStatus(400)
|
||||||
if err := mulekick.Bind(w, r, &user); err != nil {
|
if err := mulekick.Bind(w, r, &user); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := db.Mysql.Exec("update user set name=?, username=?, email=? where id=?", user.Name, user.Username, user.Email, oldUser.ID); err != nil {
|
if _, err := db.Mysql.Exec("update user set name=?, username=?, email=?, alert=? where id=?", user.Name, user.Username, user.Email, user.Alert, oldUser.ID); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +84,12 @@ func updateUserPassword(w http.ResponseWriter, r *http.Request) {
|
|||||||
Pwd string `json:"password"`
|
Pwd string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.External == true {
|
||||||
|
log.Warn("Password is not editable for external LDAP users")
|
||||||
|
c.AbortWithStatus(400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := mulekick.Bind(w, r, &pwd); err != nil {
|
if err := mulekick.Bind(w, r, &pwd); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ type Project struct {
|
|||||||
ID int `db:"id" json:"id"`
|
ID int `db:"id" json:"id"`
|
||||||
Name string `db:"name" json:"name" binding:"required"`
|
Name string `db:"name" json:"name" binding:"required"`
|
||||||
Created time.Time `db:"created" json:"created"`
|
Created time.Time `db:"created" json:"created"`
|
||||||
|
Alert bool `db:"alert" json:"alert"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (project *Project) CreateProject() error {
|
func (project *Project) CreateProject() error {
|
||||||
|
@ -11,6 +11,8 @@ type User struct {
|
|||||||
Name string `db:"name" json:"name" binding:"required"`
|
Name string `db:"name" json:"name" binding:"required"`
|
||||||
Email string `db:"email" json:"email" binding:"required"`
|
Email string `db:"email" json:"email" binding:"required"`
|
||||||
Password string `db:"password" json:"-"`
|
Password string `db:"password" json:"-"`
|
||||||
|
External bool `db:"external" json:"external"`
|
||||||
|
Alert bool `db:"alert" json:"alert"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchUser(userID int) (*User, error) {
|
func FetchUser(userID int) (*User, error) {
|
||||||
|
4
db/migrations/v2.3.0.sql
Normal file
4
db/migrations/v2.3.0.sql
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE user ADD alert BOOLEAN NOT NULL AFTER password;
|
||||||
|
ALTER TABLE project ADD alert BOOLEAN NOT NULL AFTER name;
|
||||||
|
|
||||||
|
ALTER TABLE user ADD external BOOLEAN NOT NULL AFTER password;
|
@ -67,5 +67,6 @@ func init() {
|
|||||||
{Major: 1, Minor: 8},
|
{Major: 1, Minor: 8},
|
||||||
{Major: 1, Minor: 9},
|
{Major: 1, Minor: 9},
|
||||||
{Major: 2, Minor: 2, Patch: 1},
|
{Major: 2, Minor: 2, Patch: 1},
|
||||||
|
{Major: 2, Minor: 3},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
make.sh
3
make.sh
@ -22,7 +22,8 @@ if [ "$1" == "ci_test" ]; then
|
|||||||
"name": "circle_test"
|
"name": "circle_test"
|
||||||
},
|
},
|
||||||
"session_db": "127.0.0.1:6379",
|
"session_db": "127.0.0.1:6379",
|
||||||
"port": ":8010"
|
"port": ":8010",
|
||||||
|
"email_alert": false
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
@ -6,9 +6,13 @@ form.form-horizontal
|
|||||||
.col-sm-6
|
.col-sm-6
|
||||||
input.form-control(type="text" ng-model="projectName" placeholder="Project Name")
|
input.form-control(type="text" ng-model="projectName" placeholder="Project Name")
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label.control-label.col-sm-4 Allow alerts for this project
|
||||||
|
.col-sm-8: input.checkbox-inline(type="checkbox" title="Send email alerts about failed tasks" ng-model="alert")
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
.col-sm-6.col-sm-offset-4
|
.col-sm-6.col-sm-offset-4
|
||||||
button.btn.btn-success(ng-click="save(projectName)") Save
|
button.btn.btn-success(ng-click="save(projectName, alert)") Save
|
||||||
|
|
||||||
hr
|
hr
|
||||||
|
|
||||||
|
@ -8,13 +8,18 @@
|
|||||||
.col-sm-8: input.form-control(type="text" placeholder="Your name" ng-model="user.name")
|
.col-sm-8: input.form-control(type="text" placeholder="Your name" ng-model="user.name")
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label.col-sm-4 Username
|
label.control-label.col-sm-4 Username
|
||||||
.col-sm-8: input.form-control(type="text" placeholder="Username" ng-model="user.username")
|
.col-sm-8: input.form-control(type="text" placeholder="Username" ng-model="user.username" ng-if="user.external==false")
|
||||||
|
.col-sm-8: input.form-control(type="text" placeholder="Username" ng-model="user.username" readonly="readonly" ng-if="user.external==true")
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label.col-sm-4 Email
|
label.control-label.col-sm-4 Email
|
||||||
.col-sm-8: input.form-control(type="email" placeholder="Email address" ng-model="user.email")
|
.col-sm-8: input.form-control(type="email" placeholder="Email address" ng-model="user.email")
|
||||||
.form-group
|
.form-group
|
||||||
label.control-label.col-sm-4 Password
|
label.control-label.col-sm-4 Password
|
||||||
.col-sm-8: input.form-control(type="password" placeholder="Enter new password" ng-model="user.password")
|
.col-sm-8: input.form-control(type="password" placeholder="Not editable for LDAP user" readonly="readonly" ng-model="user.password" ng-if="user.external==true")
|
||||||
|
.col-sm-8: input.form-control(type="password" placeholder="Enter new password" ng-model="user.password" ng-if="user.external==false")
|
||||||
|
.form-group
|
||||||
|
label.control-label.col-sm-4 Send alerts
|
||||||
|
.col-sm-8: input.checkbox-inline(type="checkbox" title="Send email alerts about failed tasks" ng-model="user.alert")
|
||||||
.form-group: .col-sm-8.col-sm-offset-4
|
.form-group: .col-sm-8.col-sm-offset-4
|
||||||
button.btn.btn-success(ng-click="updateUser()") Update Profile
|
button.btn.btn-success(ng-click="updateUser()") Update Profile
|
||||||
button.btn.btn-default(ng-if="$state.includes('users.user')" ui-sref="users.list") back
|
button.btn.btn-default(ng-if="$state.includes('users.user')" ui-sref="users.list") back
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
define(function () {
|
define(function () {
|
||||||
app.registerController('ProjectEditCtrl', ['$scope', '$http', 'Project', '$state', function ($scope, $http, Project, $state) {
|
app.registerController('ProjectEditCtrl', ['$scope', '$http', 'Project', '$state', function ($scope, $http, Project, $state) {
|
||||||
$scope.projectName = Project.name;
|
$scope.projectName = Project.name;
|
||||||
|
$scope.alert = Project.alert;
|
||||||
|
|
||||||
$scope.save = function (name) {
|
console.log(Project.name);
|
||||||
$http.put(Project.getURL(), { name: name }).success(function () {
|
console.log(Project);
|
||||||
|
|
||||||
|
$scope.save = function (name, alert) {
|
||||||
|
$http.put(Project.getURL(), { name: name, alert: alert }).success(function () {
|
||||||
swal('Saved', 'Project settings saved.', 'success');
|
swal('Saved', 'Project settings saved.', 'success');
|
||||||
}).error(function () {
|
}).error(function () {
|
||||||
swal('Error', 'Project settings were not saved', 'error');
|
swal('Error', 'Project settings were not saved', 'error');
|
||||||
|
@ -2,6 +2,7 @@ app.factory('ProjectFactory', ['$http', function ($http) {
|
|||||||
var Project = function (project) {
|
var Project = function (project) {
|
||||||
this.id = project.id;
|
this.id = project.id;
|
||||||
this.name = project.name;
|
this.name = project.name;
|
||||||
|
this.alert = project.alert;
|
||||||
}
|
}
|
||||||
|
|
||||||
Project.prototype.getURL = function () {
|
Project.prototype.getURL = function () {
|
||||||
|
54
scripts/docker-startup.sh
Normal file → Executable file
54
scripts/docker-startup.sh
Normal file → Executable file
@ -2,21 +2,41 @@
|
|||||||
|
|
||||||
echoerr() { printf "%s\n" "$*" >&2; }
|
echoerr() { printf "%s\n" "$*" >&2; }
|
||||||
|
|
||||||
|
SEMAPHORE_PLAYBOOK_PATH="${SEMAPHORE_PLAYBOOK_PATH:-/semaphore}"
|
||||||
|
# Semaphore database env config
|
||||||
|
SEMAPHORE_DB_HOST="${SEMAPHORE_DB_HOST:-127.0.0.1}"
|
||||||
|
SEMAPHORE_DB_PORT="${SEMAPHORE_DB_PORT:-3306}"
|
||||||
|
SEMAPHORE_DB="${SEMAPHORE_DB:-semaphore}"
|
||||||
|
SEMAPHORE_DB_USER="${SEMAPHORE_DB_USER:-semaphore}"
|
||||||
|
SEMAPHORE_DB_PASS="${SEMAPHORE_DB_PASS:-semaphore}"
|
||||||
|
# Semaphore Admin env config
|
||||||
|
SEMAPHORE_ADMIN="${SEMAPHORE_ADMIN:-admin}"
|
||||||
|
SEMAPHORE_ADMIN_EMAIL="${SEMAPHORE_ADMIN_EMAIL:-admin@localhost}"
|
||||||
|
SEMAPHORE_ADMIN_NAME="${SEMAPHORE_ADMIN_NAME:-Semaphore Admin}"
|
||||||
|
SEMAPHORE_ADMIN_PASSWORD="${SEMAPHORE_ADMIN_PASSWORD:-semaphorepassword}"
|
||||||
|
|
||||||
|
# create semaphore playbook directory
|
||||||
|
mkdir -p "${SEMAPHORE_PLAYBOOK_PATH}" || {
|
||||||
|
echo "Can't create Semaphore playbook path '$SEMAPHORE_PLAYBOOK_PATH'."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# wait on db to be up
|
# wait on db to be up
|
||||||
echoerr "Attempting to connect to database ${SEMAPHORE_DB} on ${SEMAPHORE_DB_HOST} with user:pass ${SEMAPHORE_DB_USER}:${SEMAPHORE_DB_PASS}"
|
echoerr "Attempting to connect to database ${SEMAPHORE_DB} on ${SEMAPHORE_DB_HOST}:${SEMAPHORE_DB_PORT} with user ${SEMAPHORE_DB_USER} ..."
|
||||||
until mysql -h ${SEMAPHORE_DB_HOST} -u ${SEMAPHORE_DB_USER} --password=${SEMAPHORE_DB_PASS} ${SEMAPHORE_DB} -e "select version();" &>/dev/null;
|
TIMEOUT=30
|
||||||
do
|
while ! mysqladmin ping -h"$SEMAPHORE_DB_HOST" -P "$SEMAPHORE_DB_PORT" -u "$SEMAPHORE_DB_USER" --password="$SEMAPHORE_DB_PASS" --silent >/dev/null 2>&1; do
|
||||||
echoerr "waiting";
|
TIMEOUT=$(expr $TIMEOUT - 1)
|
||||||
sleep 3;
|
if [ $TIMEOUT -eq 0 ]; then
|
||||||
|
echoerr "Could not connect to database server. Exiting."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -n "."
|
||||||
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
# generate stdin
|
if [ ! -f "${SEMAPHORE_PLAYBOOK_PATH}/semaphore_config.json" ]; then
|
||||||
if [ -f ${SEMAPHORE_PLAYBOOK_PATH}/config.stdin ]
|
echoerr "Generating ${SEMAPHORE_PLAYBOOK_PATH}/config.stdin ..."
|
||||||
then
|
cat << EOF > "${SEMAPHORE_PLAYBOOK_PATH}/config.stdin"
|
||||||
echoerr "already generated stdin"
|
|
||||||
else
|
|
||||||
echoerr "generating ${SEMAPHORE_PLAYBOOK_PATH}/config.stdin"
|
|
||||||
cat << EOF > ${SEMAPHORE_PLAYBOOK_PATH}/config.stdin
|
|
||||||
${SEMAPHORE_DB_HOST}:${SEMAPHORE_DB_PORT}
|
${SEMAPHORE_DB_HOST}:${SEMAPHORE_DB_PORT}
|
||||||
${SEMAPHORE_DB_USER}
|
${SEMAPHORE_DB_USER}
|
||||||
${SEMAPHORE_DB_PASS}
|
${SEMAPHORE_DB_PASS}
|
||||||
@ -28,15 +48,9 @@ ${SEMAPHORE_ADMIN_EMAIL}
|
|||||||
${SEMAPHORE_ADMIN_NAME}
|
${SEMAPHORE_ADMIN_NAME}
|
||||||
${SEMAPHORE_ADMIN_PASSWORD}
|
${SEMAPHORE_ADMIN_PASSWORD}
|
||||||
EOF
|
EOF
|
||||||
fi
|
/usr/bin/semaphore -setup < "${SEMAPHORE_PLAYBOOK_PATH}/config.stdin"
|
||||||
|
|
||||||
# test to see if initialzation is needed
|
ln -s "${SEMAPHORE_PLAYBOOK_PATH}/semaphore_config.json" /etc/semaphore/semaphore_config.json
|
||||||
if [ -f ${SEMAPHORE_PLAYBOOK_PATH}/semaphore_config.json ]
|
|
||||||
then
|
|
||||||
echoerr "already initialized"
|
|
||||||
else
|
|
||||||
echoerr "Initializing semaphore"
|
|
||||||
/usr/bin/semaphore -setup < ${SEMAPHORE_PLAYBOOK_PATH}/config.stdin
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# run our command
|
# run our command
|
||||||
|
179
util/config.go
179
util/config.go
@ -26,6 +26,13 @@ type mySQLConfig struct {
|
|||||||
DbName string `json:"name"`
|
DbName string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ldapMappings struct {
|
||||||
|
DN string `json:"dn"`
|
||||||
|
Mail string `json:"mail"`
|
||||||
|
Uid string `json:"uid"`
|
||||||
|
CN string `json:"cn"`
|
||||||
|
}
|
||||||
|
|
||||||
type configType struct {
|
type configType struct {
|
||||||
MySQL mySQLConfig `json:"mysql"`
|
MySQL mySQLConfig `json:"mysql"`
|
||||||
// Format `:port_num` eg, :3000
|
// Format `:port_num` eg, :3000
|
||||||
@ -38,6 +45,30 @@ type configType struct {
|
|||||||
// cookie hashing & encryption
|
// cookie hashing & encryption
|
||||||
CookieHash string `json:"cookie_hash"`
|
CookieHash string `json:"cookie_hash"`
|
||||||
CookieEncryption string `json:"cookie_encryption"`
|
CookieEncryption string `json:"cookie_encryption"`
|
||||||
|
|
||||||
|
//email alerting
|
||||||
|
EmailAlert bool `json:"email_alert"`
|
||||||
|
EmailSender string `json:"email_sender"`
|
||||||
|
EmailHost string `json:"email_host"`
|
||||||
|
EmailPort string `json:"email_port"`
|
||||||
|
|
||||||
|
//web host
|
||||||
|
WebHost string `json:"web_host"`
|
||||||
|
|
||||||
|
//ldap settings
|
||||||
|
LdapEnable bool `json:"ldap_enable"`
|
||||||
|
LdapBindDN string `json:"ldap_binddn"`
|
||||||
|
LdapBindPassword string `json:"ldap_bindpassword"`
|
||||||
|
LdapServer string `json:"ldap_server"`
|
||||||
|
LdapNeedTLS bool `json:"ldap_needtls"`
|
||||||
|
LdapSearchDN string `json:"ldap_searchdn"`
|
||||||
|
LdapSearchFilter string `json:"ldap_searchfilter"`
|
||||||
|
LdapMappings ldapMappings `json:"ldap_mappings"`
|
||||||
|
|
||||||
|
//telegram alerting
|
||||||
|
TelegramAlert bool `json:"telegram_alert"`
|
||||||
|
TelegramChat string `json:"telegram_chat"`
|
||||||
|
TelegramToken string `json:"telegram_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var Config *configType
|
var Config *configType
|
||||||
@ -182,4 +213,152 @@ func (conf *configType) Scan() {
|
|||||||
conf.TmpPath = "/tmp/semaphore"
|
conf.TmpPath = "/tmp/semaphore"
|
||||||
}
|
}
|
||||||
conf.TmpPath = path.Clean(conf.TmpPath)
|
conf.TmpPath = path.Clean(conf.TmpPath)
|
||||||
|
|
||||||
|
fmt.Print(" > Web root URL (default http://localhost:8010/): ")
|
||||||
|
fmt.Scanln(&conf.WebHost)
|
||||||
|
|
||||||
|
if len(conf.WebHost) == 0 {
|
||||||
|
conf.WebHost = "http://localhost:8010/"
|
||||||
|
}
|
||||||
|
|
||||||
|
var EmailAlertAnswer string
|
||||||
|
fmt.Print(" > Enable email alerts (y/n, default n): ")
|
||||||
|
fmt.Scanln(&EmailAlertAnswer)
|
||||||
|
if EmailAlertAnswer == "yes" || EmailAlertAnswer == "y" {
|
||||||
|
|
||||||
|
conf.EmailAlert = true
|
||||||
|
|
||||||
|
fmt.Print(" > Mail server host (default localhost): ")
|
||||||
|
fmt.Scanln(&conf.EmailHost)
|
||||||
|
|
||||||
|
if len(conf.EmailHost) == 0 {
|
||||||
|
conf.EmailHost = "localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > Mail server port (default 25): ")
|
||||||
|
fmt.Scanln(&conf.EmailPort)
|
||||||
|
|
||||||
|
if len(conf.EmailPort) == 0 {
|
||||||
|
conf.EmailPort = "25"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > Mail sender address (default semaphore@localhost): ")
|
||||||
|
fmt.Scanln(&conf.EmailSender)
|
||||||
|
|
||||||
|
if len(conf.EmailSender) == 0 {
|
||||||
|
conf.EmailSender = "semaphore@localhost"
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
conf.EmailAlert = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var TelegramAlertAnswer string
|
||||||
|
fmt.Print(" > Enable telegram alerts (y/n, default n): ")
|
||||||
|
fmt.Scanln(&TelegramAlertAnswer)
|
||||||
|
if TelegramAlertAnswer == "yes" || TelegramAlertAnswer == "y" {
|
||||||
|
|
||||||
|
conf.TelegramAlert = true
|
||||||
|
|
||||||
|
fmt.Print(" > Telegram bot token (you can get it from @BotFather) (default ''): ")
|
||||||
|
fmt.Scanln(&conf.TelegramToken)
|
||||||
|
|
||||||
|
if len(conf.TelegramToken) == 0 {
|
||||||
|
conf.TelegramToken = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > Telegram chat ID (default ''): ")
|
||||||
|
fmt.Scanln(&conf.TelegramChat)
|
||||||
|
|
||||||
|
if len(conf.TelegramChat) == 0 {
|
||||||
|
conf.TelegramChat = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
conf.TelegramAlert = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var LdapAnswer string
|
||||||
|
fmt.Print(" > Enable LDAP authentication (y/n, default n): ")
|
||||||
|
fmt.Scanln(&LdapAnswer)
|
||||||
|
if LdapAnswer == "yes" || LdapAnswer == "y" {
|
||||||
|
|
||||||
|
conf.LdapEnable = true
|
||||||
|
|
||||||
|
fmt.Print(" > LDAP server host (default localhost:389): ")
|
||||||
|
fmt.Scanln(&conf.LdapServer)
|
||||||
|
|
||||||
|
if len(conf.LdapServer) == 0 {
|
||||||
|
conf.LdapServer = "localhost:389"
|
||||||
|
}
|
||||||
|
|
||||||
|
var LdapTLSAnswer string
|
||||||
|
fmt.Print(" > Enable LDAP TLS connection (y/n, default n): ")
|
||||||
|
fmt.Scanln(&LdapTLSAnswer)
|
||||||
|
if LdapTLSAnswer == "yes" || LdapTLSAnswer == "y" {
|
||||||
|
conf.LdapNeedTLS = true
|
||||||
|
} else {
|
||||||
|
conf.LdapNeedTLS = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > LDAP DN for bind (default cn=user,ou=users,dc=example): ")
|
||||||
|
fmt.Scanln(&conf.LdapBindDN)
|
||||||
|
|
||||||
|
if len(conf.LdapBindDN) == 0 {
|
||||||
|
conf.LdapBindDN = "cn=user,ou=users,dc=example"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > Password for LDAP bind user (default pa55w0rd): ")
|
||||||
|
fmt.Scanln(&conf.LdapBindPassword)
|
||||||
|
|
||||||
|
if len(conf.LdapBindPassword) == 0 {
|
||||||
|
conf.LdapBindPassword = "pa55w0rd"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > LDAP DN for user search (default ou=users,dc=example): ")
|
||||||
|
fmt.Scanln(&conf.LdapSearchDN)
|
||||||
|
|
||||||
|
if len(conf.LdapSearchDN) == 0 {
|
||||||
|
conf.LdapSearchDN = "ou=users,dc=example"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > LDAP search filter (default (uid=" + "%" + "s)): ")
|
||||||
|
fmt.Scanln(&conf.LdapSearchFilter)
|
||||||
|
|
||||||
|
if len(conf.LdapSearchFilter) == 0 {
|
||||||
|
conf.LdapSearchFilter = "(uid=%s)"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > LDAP mapping for DN field (default dn): ")
|
||||||
|
fmt.Scanln(&conf.LdapMappings.DN)
|
||||||
|
|
||||||
|
if len(conf.LdapMappings.DN) == 0 {
|
||||||
|
conf.LdapMappings.DN = "dn"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > LDAP mapping for username field (default uid): ")
|
||||||
|
fmt.Scanln(&conf.LdapMappings.Uid)
|
||||||
|
|
||||||
|
if len(conf.LdapMappings.Uid) == 0 {
|
||||||
|
conf.LdapMappings.Uid = "uid"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > LDAP mapping for full name field (default cn): ")
|
||||||
|
fmt.Scanln(&conf.LdapMappings.CN)
|
||||||
|
|
||||||
|
if len(conf.LdapMappings.CN) == 0 {
|
||||||
|
conf.LdapMappings.CN = "cn"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Print(" > LDAP mapping for email field (default mail): ")
|
||||||
|
fmt.Scanln(&conf.LdapMappings.Mail)
|
||||||
|
|
||||||
|
if len(conf.LdapMappings.Mail) == 0 {
|
||||||
|
conf.LdapMappings.Mail = "mail"
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
conf.LdapEnable = false
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
32
util/mail.go
Normal file
32
util/mail.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"net/smtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SendMail(emailHost, mailSender, mailRecipient string, mail bytes.Buffer) error {
|
||||||
|
|
||||||
|
c, err := smtp.Dial(emailHost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer c.Close()
|
||||||
|
// Set the sender and recipient.
|
||||||
|
c.Mail(mailSender)
|
||||||
|
c.Rcpt(mailRecipient)
|
||||||
|
|
||||||
|
// Send the email body.
|
||||||
|
wc, err := c.Data()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer wc.Close()
|
||||||
|
if _, err = mail.WriteTo(wc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user