/
api.go
123 lines (100 loc) · 3.01 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package api
import (
"encoding/base64"
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
log "github.com/sirupsen/logrus"
"github.com/rs/cors"
"github.com/camptocamp/terradb/internal/storage"
)
// API defines an API struct
type API struct {
Address string
Port string
Username string
Password string
PageSize int
}
type server struct {
st storage.Storage
pageSize int
username string
password string
}
// StartServer starts the API server
func StartServer(cfg *API, st storage.Storage) {
s := server{
st: st,
pageSize: cfg.PageSize,
username: cfg.Username,
password: cfg.Password,
}
if !authenticationRequired(s.username, s.password) {
log.Warning("Authentication disabled: empty username or password.")
}
router := mux.NewRouter().StrictSlash(true)
router.Use(s.handleAPIRequest)
apiRtr := router.PathPrefix("/v1").Subrouter()
apiRtr.HandleFunc("/states", s.ListStates).Methods("GET")
apiRtr.HandleFunc("/states/{name}", s.InsertState).Methods("POST")
apiRtr.HandleFunc("/states/{name}", s.GetState).Methods("GET")
apiRtr.HandleFunc("/states/{name}", s.RemoveState).Methods("DELETE")
apiRtr.HandleFunc("/states/{name}", s.LockState).Methods("LOCK")
apiRtr.HandleFunc("/states/{name}", s.UnlockState).Methods("UNLOCK")
apiRtr.HandleFunc("/states/{name}/serials", s.ListStateSerials).Methods("GET")
apiRtr.HandleFunc("/resources/{state}/{module}/{name}", s.GetResource).Methods("GET")
apiRtr.HandleFunc("/resources/{state}/{name}", s.GetResource).Methods("GET")
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
})
handler := c.Handler(router)
log.Infof("Listening on %s:%s", cfg.Address, cfg.Port)
log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%s", cfg.Address, cfg.Port), handler))
return
}
func (s *server) handleAPIRequest(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json")
if authenticationRequired(s.username, s.password) {
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
if !isAuthorized(r.Header.Get("Authorization"), s.username, s.password) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("401 - Not authorized"))
return
}
}
next.ServeHTTP(w, r)
})
}
func err500(err error, msg string, w http.ResponseWriter) {
log.Errorf("%s: %s", msg, err)
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(fmt.Sprintf("500 - Internal server error: %s", err)))
return
}
func authenticationRequired(username, password string) bool {
if username == "" || password == "" {
return false
}
return true
}
func isAuthorized(authorizationHeader, username, password string) bool {
s := strings.SplitN(authorizationHeader, " ", 2)
if len(s) != 2 {
return false
}
b, err := base64.StdEncoding.DecodeString(s[1])
if err != nil {
return false
}
pair := strings.SplitN(string(b), ":", 2)
if len(pair) != 2 {
return false
}
if pair[0] != username || pair[1] != password {
return false
}
return true
}