2016-05-24 11:55:48 +02:00
|
|
|
package api
|
2016-01-05 00:32:53 +01:00
|
|
|
|
|
|
|
import (
|
2019-07-09 18:14:06 +02:00
|
|
|
"fmt"
|
2017-02-23 00:21:49 +01:00
|
|
|
"net/http"
|
2016-01-06 12:20:07 +01:00
|
|
|
"strings"
|
2016-03-16 22:49:43 +01:00
|
|
|
|
2016-05-24 11:55:48 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/api/projects"
|
|
|
|
"github.com/ansible-semaphore/semaphore/api/sockets"
|
|
|
|
"github.com/ansible-semaphore/semaphore/api/tasks"
|
2019-07-09 09:21:49 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/mulekick"
|
2016-03-16 22:49:43 +01:00
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
2018-03-05 19:59:58 +01:00
|
|
|
"github.com/gobuffalo/packr"
|
2017-02-23 00:21:49 +01:00
|
|
|
"github.com/gorilla/mux"
|
2016-05-24 11:55:48 +02:00
|
|
|
"github.com/russross/blackfriday"
|
2016-01-05 00:32:53 +01:00
|
|
|
)
|
|
|
|
|
2018-03-12 13:27:13 +01:00
|
|
|
var publicAssets = packr.NewBox("../web/public")
|
2018-03-05 19:59:58 +01:00
|
|
|
|
2018-04-11 20:05:38 +02:00
|
|
|
//JSONMiddleware ensures that all the routes respond with Json, this is added by default to all routes
|
2019-07-09 09:21:49 +02:00
|
|
|
func JSONMiddleware(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("content-type", "application/json")
|
2019-07-09 18:11:01 +02:00
|
|
|
next.ServeHTTP(w, r)
|
2019-07-09 09:21:49 +02:00
|
|
|
})
|
2018-04-11 20:05:38 +02:00
|
|
|
}
|
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
//plainTextMiddleware resets headers to Plain Text if needed
|
|
|
|
func plainTextMiddleware(next http.Handler) http.Handler {
|
2019-07-09 09:21:49 +02:00
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("content-type", "text/plain; charset=utf-8")
|
2019-07-09 18:11:01 +02:00
|
|
|
next.ServeHTTP(w, r)
|
2019-07-09 09:21:49 +02:00
|
|
|
})
|
2018-04-11 20:05:38 +02:00
|
|
|
}
|
2018-10-23 05:22:49 +02:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
func pongHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Write([]byte("pong"))
|
|
|
|
}
|
|
|
|
|
|
|
|
func notFoundHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("Access-Control-Allow-Origin", r.Header.Get("Origin"))
|
|
|
|
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
w.Write([]byte("404 not found"))
|
|
|
|
fmt.Println(r.Method, ":", r.URL.String(), "--> 404 Not Found")
|
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
// Route declares all routes
|
2019-07-09 18:14:06 +02:00
|
|
|
func Route() *mux.Router {
|
|
|
|
r := mux.NewRouter()
|
2019-07-09 18:11:01 +02:00
|
|
|
r.NotFoundHandler = http.HandlerFunc(servePublic)
|
2016-01-05 00:32:53 +01:00
|
|
|
|
2018-10-24 07:04:55 +02:00
|
|
|
webPath := "/"
|
|
|
|
if util.WebHostURL != nil {
|
|
|
|
webPath = util.WebHostURL.RequestURI()
|
|
|
|
}
|
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
r.HandleFunc(webPath, http.HandlerFunc(servePublic))
|
|
|
|
r.Use(mux.CORSMethodMiddleware(r), JSONMiddleware)
|
2016-01-05 00:32:53 +01:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
r.HandleFunc("/api/auth/login", login).Methods("POST")
|
|
|
|
r.HandleFunc("/api/auth/logout", logout).Methods("POST")
|
|
|
|
r.HandleFunc("/api/ping", pongHandler).Methods("GET", "HEAD").Subrouter().Use(plainTextMiddleware)
|
2019-07-09 18:11:01 +02:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
// set up the namespace
|
|
|
|
api := r.PathPrefix(webPath + "api").Subrouter()
|
2016-01-05 00:32:53 +01:00
|
|
|
|
2016-04-30 14:28:47 +02:00
|
|
|
api.Use(authentication)
|
2016-01-05 00:32:53 +01:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
api.HandleFunc("/ws", sockets.Handler).Methods("GET", "HEAD")
|
|
|
|
api.HandleFunc("/info", getSystemInfo).Methods("GET", "HEAD")
|
|
|
|
api.HandleFunc("/upgrade", checkUpgrade).Methods("GET", "HEAD")
|
|
|
|
api.HandleFunc("/upgrade", doUpgrade).Methods("POST")
|
|
|
|
|
|
|
|
user := api.PathPrefix("/user").Subrouter()
|
|
|
|
|
|
|
|
user.HandleFunc("", getUser).Methods("GET", "HEAD")
|
|
|
|
user.HandleFunc("/tokens", getAPITokens).Methods("GET", "HEAD")
|
|
|
|
user.HandleFunc("/tokens", createAPIToken).Methods("POST")
|
|
|
|
user.HandleFunc("/tokens/{token_id}", expireAPIToken).Methods("DELETE")
|
|
|
|
|
|
|
|
api.HandleFunc("/projects", projects.GetProjects).Methods("GET", "HEAD")
|
|
|
|
api.HandleFunc("/projects", projects.AddProject).Methods("POST")
|
|
|
|
api.HandleFunc("/events", getAllEvents).Methods("GET", "HEAD")
|
|
|
|
api.HandleFunc("/events/last", getLastEvents).Methods("GET", "HEAD")
|
|
|
|
|
|
|
|
api.HandleFunc("/users", getUsers).Methods("GET", "HEAD")
|
|
|
|
api.HandleFunc("/users", addUser).Methods("POST")
|
|
|
|
api.HandleFunc("/users/{user_id}", getUser).Methods("GET", "HEAD").Subrouter().Use(getUserMiddleware)
|
|
|
|
api.HandleFunc("/users/{user_id}", updateUser).Methods("PUT").Subrouter().Use(getUserMiddleware)
|
|
|
|
api.HandleFunc("/users/{user_id}/password", updateUserPassword).Methods("POST").Subrouter().Use(getUserMiddleware)
|
|
|
|
api.HandleFunc("/users/{user_id}", deleteUser).Methods("DELETE").Subrouter().Use(getUserMiddleware)
|
|
|
|
|
|
|
|
project := api.PathPrefix("/project/{project_id}").Subrouter()
|
|
|
|
|
|
|
|
project.Use(projects.ProjectMiddleware)
|
|
|
|
|
|
|
|
project.HandleFunc("", projects.GetProject).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("", projects.UpdateProject).Methods("PUT").Subrouter().Use(projects.MustBeAdmin)
|
|
|
|
project.HandleFunc("", projects.DeleteProject).Methods("DELETE").Subrouter().Use(projects.MustBeAdmin)
|
|
|
|
|
|
|
|
project.HandleFunc("/events", getAllEvents).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("/events/last", getLastEvents).Methods("GET", "HEAD")
|
|
|
|
|
|
|
|
project.HandleFunc("/users", projects.GetUsers).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("/users", projects.AddUser).Methods("POST").Subrouter().Use(projects.MustBeAdmin)
|
|
|
|
project.HandleFunc("/users/{user_id}/admin", projects.MakeUserAdmin).Methods("POST").Subrouter().Use(projects.UserMiddleware, projects.MustBeAdmin)
|
|
|
|
project.HandleFunc("/users/{user_id}/admin", projects.MakeUserAdmin).Methods("DELETE").Subrouter().Use(projects.UserMiddleware, projects.MustBeAdmin)
|
|
|
|
project.HandleFunc("/users/{user_id}", projects.RemoveUser).Methods("DELETE").Subrouter().Use(projects.UserMiddleware, projects.MustBeAdmin)
|
|
|
|
|
|
|
|
project.HandleFunc("/keys", projects.GetKeys).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("/keys", projects.AddKey).Methods("POST")
|
|
|
|
project.HandleFunc("/keys/{key_id}", projects.UpdateKey).Methods("PUT").Subrouter().Use(projects.KeyMiddleware)
|
|
|
|
project.HandleFunc("/keys/{key_id}", projects.RemoveKey).Methods("DELETE").Subrouter().Use(projects.KeyMiddleware)
|
|
|
|
|
|
|
|
project.HandleFunc("/repositories", projects.GetRepositories).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("/repositories", projects.AddRepository).Methods("POST")
|
|
|
|
project.HandleFunc("/repositories/{repository_id}", projects.UpdateRepository).Methods("PUT").Subrouter().Use(projects.RepositoryMiddleware)
|
|
|
|
project.HandleFunc("/repositories/{repository_id}", projects.RemoveRepository).Methods("DELETE").Subrouter().Use(projects.RepositoryMiddleware)
|
|
|
|
|
|
|
|
project.HandleFunc("/inventory", projects.GetInventory).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("/inventory", projects.AddInventory).Methods("POST")
|
|
|
|
project.HandleFunc("/inventory/{inventory_id}", projects.UpdateInventory).Methods("PUT").Subrouter().Use(projects.InventoryMiddleware)
|
|
|
|
project.HandleFunc("/inventory/{inventory_id}", projects.RemoveInventory).Methods("DELETE").Subrouter().Use(projects.InventoryMiddleware)
|
|
|
|
|
|
|
|
project.HandleFunc("/environment", projects.GetEnvironment).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("/environment", projects.AddEnvironment).Methods("POST")
|
|
|
|
project.HandleFunc("/environment/{environment_id}", projects.UpdateEnvironment).Methods("PUT").Subrouter().Use(projects.EnvironmentMiddleware)
|
|
|
|
project.HandleFunc("/environment/{environment_id}", projects.RemoveEnvironment).Methods("DELETE").Subrouter().Use(projects.EnvironmentMiddleware)
|
|
|
|
|
|
|
|
project.HandleFunc("/templates", projects.GetTemplates).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("/templates", projects.AddTemplate).Methods("POST")
|
|
|
|
project.HandleFunc("/templates/{template_id}", projects.UpdateTemplate).Methods("PUT").Subrouter().Use(projects.TemplatesMiddleware)
|
|
|
|
project.HandleFunc("/templates/{template_id}", projects.RemoveTemplate).Methods("DELETE").Subrouter().Use(projects.TemplatesMiddleware)
|
|
|
|
|
|
|
|
project.HandleFunc("/tasks", tasks.GetAllTasks).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("/tasks/last", tasks.GetLastTasks).Methods("GET", "HEAD")
|
|
|
|
project.HandleFunc("/tasks", tasks.AddTask).Methods("POST")
|
|
|
|
project.HandleFunc("/tasks/{task_id}/output", tasks.GetTaskOutput).Methods("GET", "HEAD").Subrouter().Use(tasks.GetTaskMiddleware)
|
|
|
|
project.HandleFunc("/tasks/{task_id}", tasks.GetTask).Methods("GET", "HEAD").Subrouter().Use(tasks.GetTaskMiddleware)
|
|
|
|
project.HandleFunc("/tasks/{task_id}", tasks.RemoveTask).Methods("DELETE").Subrouter().Use(tasks.GetTaskMiddleware)
|
2019-07-09 18:11:01 +02:00
|
|
|
|
2017-02-23 00:21:49 +01:00
|
|
|
return r
|
2016-01-05 00:32:53 +01:00
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
//nolint: gocyclo
|
2019-07-09 18:11:01 +02:00
|
|
|
func servePublic(w http.ResponseWriter, r *http.Request) {
|
|
|
|
path := r.URL.Path
|
2016-04-04 15:44:34 +02:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
if strings.HasPrefix(path, "/api") {
|
2019-07-09 18:14:06 +02:00
|
|
|
notFoundHandler(w, r)
|
2019-07-09 18:11:01 +02:00
|
|
|
return
|
|
|
|
}
|
2016-01-06 12:20:07 +01:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
webPath := "/"
|
|
|
|
if util.WebHostURL != nil {
|
|
|
|
webPath = util.WebHostURL.RequestURI()
|
|
|
|
}
|
2016-01-06 12:20:07 +01:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
if !strings.HasPrefix(path, webPath+"public") {
|
|
|
|
if len(strings.Split(path, ".")) > 1 {
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
return
|
2019-07-09 14:56:03 +02:00
|
|
|
}
|
2016-01-06 12:20:07 +01:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
path = "/html/index.html"
|
|
|
|
}
|
2017-05-20 16:14:36 +02:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
path = strings.Replace(path, webPath+"public/", "", 1)
|
|
|
|
split := strings.Split(path, ".")
|
|
|
|
suffix := split[len(split)-1]
|
2016-01-05 00:32:53 +01:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
res, err := publicAssets.MustBytes(path)
|
|
|
|
if err != nil {
|
2019-07-09 18:14:06 +02:00
|
|
|
notFoundHandler(w, r)
|
2019-07-09 18:11:01 +02:00
|
|
|
return
|
|
|
|
}
|
2019-07-09 14:56:03 +02:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
// replace base path
|
|
|
|
if util.WebHostURL != nil && path == "/html/index.html" {
|
|
|
|
res = []byte(strings.Replace(string(res),
|
|
|
|
"<base href=\"/\">",
|
|
|
|
"<base href=\""+util.WebHostURL.String()+"\">",
|
|
|
|
1))
|
|
|
|
}
|
2019-07-09 14:56:03 +02:00
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
contentType := "text/plain"
|
|
|
|
switch suffix {
|
|
|
|
case "png":
|
|
|
|
contentType = "image/png"
|
|
|
|
case "jpg", "jpeg":
|
|
|
|
contentType = "image/jpeg"
|
|
|
|
case "gif":
|
|
|
|
contentType = "image/gif"
|
|
|
|
case "js":
|
|
|
|
contentType = "application/javascript"
|
|
|
|
case "css":
|
|
|
|
contentType = "text/css"
|
|
|
|
case "woff":
|
|
|
|
contentType = "application/x-font-woff"
|
|
|
|
case "ttf":
|
|
|
|
contentType = "application/x-font-ttf"
|
|
|
|
case "otf":
|
|
|
|
contentType = "application/x-font-otf"
|
|
|
|
case "html":
|
|
|
|
contentType = "text/html"
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("content-type", contentType)
|
|
|
|
_, err = w.Write(res)
|
|
|
|
util.LogWarning(err)
|
2016-01-05 00:32:53 +01:00
|
|
|
}
|
2016-05-17 17:18:26 +02:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
func getSystemInfo(w http.ResponseWriter, r *http.Request) {
|
|
|
|
body := map[string]interface{}{
|
|
|
|
"version": util.Version,
|
|
|
|
"update": util.UpdateAvailable,
|
|
|
|
"config": map[string]string{
|
|
|
|
"dbHost": util.Config.MySQL.Hostname,
|
|
|
|
"dbName": util.Config.MySQL.DbName,
|
|
|
|
"dbUser": util.Config.MySQL.Username,
|
|
|
|
"path": util.Config.TmpPath,
|
|
|
|
"cmdPath": util.FindSemaphore(),
|
|
|
|
},
|
|
|
|
}
|
2016-05-17 17:18:26 +02:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
if util.UpdateAvailable != nil {
|
|
|
|
body["updateBody"] = string(blackfriday.MarkdownCommon([]byte(*util.UpdateAvailable.Body)))
|
|
|
|
}
|
2016-05-17 17:18:26 +02:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
mulekick.WriteJSON(w, http.StatusOK, body)
|
2016-05-17 17:18:26 +02:00
|
|
|
}
|
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
func checkUpgrade(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if err := util.CheckUpdate(util.Version); err != nil {
|
|
|
|
mulekick.WriteJSON(w, 500, err)
|
|
|
|
return
|
|
|
|
}
|
2016-05-17 17:18:26 +02:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
if util.UpdateAvailable != nil {
|
|
|
|
getSystemInfo(w, r)
|
|
|
|
return
|
|
|
|
}
|
2016-05-17 17:18:26 +02:00
|
|
|
|
2019-07-09 18:14:06 +02:00
|
|
|
w.WriteHeader(http.StatusNoContent)
|
2016-05-17 17:18:26 +02:00
|
|
|
}
|
|
|
|
|
2019-07-09 18:11:01 +02:00
|
|
|
func doUpgrade(w http.ResponseWriter, r *http.Request) {
|
|
|
|
util.LogError(util.DoUpgrade(util.Version))
|
2016-05-17 17:18:26 +02:00
|
|
|
}
|