Commit 3fffcb954a1cc48a4bfe8823c655015cfd70c93b

Authored by Marko Tikvić
1 parent 1b7dfab733
Exists in master and in 1 other branch v2

removed old http API

1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "crypto/rand" 4 "crypto/rand"
5 "crypto/sha256" 5 "crypto/sha256"
6 "encoding/hex" 6 "encoding/hex"
7 "errors" 7 "errors"
8 "net/http" 8 "net/http"
9 "strings" 9 "strings"
10 "time" 10 "time"
11 11
12 "github.com/dgrijalva/jwt-go" 12 "github.com/dgrijalva/jwt-go"
13 ) 13 )
14 14
15 const OneDay = time.Hour * 24
16 const OneWeek = OneDay * 7
17 const saltSize = 32
18
19 var appName = "webutility" 15 var appName = "webutility"
20 var secret = "webutility" 16 var secret = "webutility"
21 17
22 type Role struct { 18 type Role struct {
23 Name string `json:"name"` 19 Name string `json:"name"`
24 ID int64 `json:"id"` 20 ID int64 `json:"id"`
25 } 21 }
26 22
27 // TokenClaims are JWT token claims. 23 // TokenClaims are JWT token claims.
28 type TokenClaims struct { 24 type TokenClaims struct {
29 // extending a struct 25 // extending a struct
30 jwt.StandardClaims 26 jwt.StandardClaims
31 27
32 // custom claims 28 // custom claims
33 Token string `json:"access_token"` 29 Token string `json:"access_token"`
34 TokenType string `json:"token_type"` 30 TokenType string `json:"token_type"`
35 Username string `json:"username"` 31 Username string `json:"username"`
36 Role string `json:"role"` 32 Role string `json:"role"`
37 RoleID int64 `json:"role_id"` 33 RoleID int64 `json:"role_id"`
38 ExpiresIn int64 `json:"expires_in"` 34 ExpiresIn int64 `json:"expires_in"`
39 } 35 }
40 36
41 func InitJWT(appName, secret string) { 37 func InitJWT(appName, secret string) {
42 appName = appName 38 appName = appName
43 secret = secret 39 secret = secret
44 } 40 }
45 41
46 // ValidateCredentials hashes pass and salt and returns comparison result with resultHash 42 // ValidateCredentials hashes pass and salt and returns comparison result with resultHash
47 func ValidateCredentials(pass, salt, resultHash string) (bool, error) { 43 func ValidateCredentials(pass, salt, resultHash string) (bool, error) {
48 hash, _, err := CreateHash(pass, salt) 44 hash, _, err := CreateHash(pass, salt)
49 if err != nil { 45 if err != nil {
50 return false, err 46 return false, err
51 } 47 }
52 res := hash == resultHash 48 res := hash == resultHash
53 return res, nil 49 return res, nil
54 } 50 }
55 51
56 // CreateHash hashes str using SHA256. 52 // CreateHash hashes str using SHA256.
57 // If the presalt parameter is not provided CreateHash will generate new salt string. 53 // If the presalt parameter is not provided CreateHash will generate new salt string.
58 // Returns hash and salt strings or an error if it fails. 54 // Returns hash and salt strings or an error if it fails.
59 func CreateHash(str, presalt string) (hash, salt string, err error) { 55 func CreateHash(str, presalt string) (hash, salt string, err error) {
60 // chech if message is presalted 56 // chech if message is presalted
61 if presalt == "" { 57 if presalt == "" {
62 salt, err = randomSalt() 58 salt, err = randomSalt()
63 if err != nil { 59 if err != nil {
64 return "", "", err 60 return "", "", err
65 } 61 }
66 } else { 62 } else {
67 salt = presalt 63 salt = presalt
68 } 64 }
69 65
70 // convert strings to raw byte slices 66 // convert strings to raw byte slices
71 rawstr := []byte(str) 67 rawstr := []byte(str)
72 rawsalt, err := hex.DecodeString(salt) 68 rawsalt, err := hex.DecodeString(salt)
73 if err != nil { 69 if err != nil {
74 return "", "", err 70 return "", "", err
75 } 71 }
76 72
77 rawdata := make([]byte, len(rawstr)+len(rawsalt)) 73 rawdata := make([]byte, len(rawstr)+len(rawsalt))
78 rawdata = append(rawdata, rawstr...) 74 rawdata = append(rawdata, rawstr...)
79 rawdata = append(rawdata, rawsalt...) 75 rawdata = append(rawdata, rawsalt...)
80 76
81 // hash message + salt 77 // hash message + salt
82 hasher := sha256.New() 78 hasher := sha256.New()
83 hasher.Write(rawdata) 79 hasher.Write(rawdata)
84 rawhash := hasher.Sum(nil) 80 rawhash := hasher.Sum(nil)
85 81
86 hash = hex.EncodeToString(rawhash) 82 hash = hex.EncodeToString(rawhash)
87 return hash, salt, nil 83 return hash, salt, nil
88 } 84 }
89 85
90 // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims. 86 // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims.
91 // It returns an error if it fails. 87 // It returns an error if it fails.
92 func CreateAuthToken(username string, role Role) (TokenClaims, error) { 88 func CreateAuthToken(username string, role Role) (TokenClaims, error) {
93 t0 := (time.Now()).Unix() 89 t0 := (time.Now()).Unix()
94 t1 := (time.Now().Add(OneWeek)).Unix() 90 t1 := (time.Now().Add(time.Hour * 24 * 7)).Unix()
95 claims := TokenClaims{ 91 claims := TokenClaims{
96 TokenType: "Bearer", 92 TokenType: "Bearer",
97 Username: username, 93 Username: username,
98 Role: role.Name, 94 Role: role.Name,
99 RoleID: role.ID, 95 RoleID: role.ID,
100 ExpiresIn: t1 - t0, 96 ExpiresIn: t1 - t0,
101 } 97 }
102 // initialize jwt.StandardClaims fields (anonymous struct) 98 // initialize jwt.StandardClaims fields (anonymous struct)
103 claims.IssuedAt = t0 99 claims.IssuedAt = t0
104 claims.ExpiresAt = t1 100 claims.ExpiresAt = t1
105 claims.Issuer = appName 101 claims.Issuer = appName
106 102
107 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 103 jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
108 token, err := jwtToken.SignedString([]byte(secret)) 104 token, err := jwtToken.SignedString([]byte(secret))
109 if err != nil { 105 if err != nil {
110 return TokenClaims{}, err 106 return TokenClaims{}, err
111 } 107 }
112 claims.Token = token 108 claims.Token = token
113 return claims, nil 109 return claims, nil
114 } 110 }
115 111
116 // RefreshAuthToken returns new JWT token with sprolongs JWT token's expiration date for one week. 112 // RefreshAuthToken returns new JWT token with same claims contained in tok but with prolonged expiration date.
117 // It returns new JWT token or an error if it fails. 113 // It returns an error if it fails.
118 func RefreshAuthToken(tok string) (TokenClaims, error) { 114 func RefreshAuthToken(tok string) (TokenClaims, error) {
119 token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc) 115 token, err := jwt.ParseWithClaims(tok, &TokenClaims{}, secretFunc)
120 if err != nil { 116 if err != nil {
121 if validation, ok := err.(*jwt.ValidationError); ok { 117 if validation, ok := err.(*jwt.ValidationError); ok {
122 // don't return error if token is expired 118 // don't return error if token is expired
123 // just extend it 119 // just extend it
124 if !(validation.Errors&jwt.ValidationErrorExpired != 0) { 120 if !(validation.Errors&jwt.ValidationErrorExpired != 0) {
125 return TokenClaims{}, err 121 return TokenClaims{}, err
126 } 122 }
127 } else { 123 } else {
128 return TokenClaims{}, err 124 return TokenClaims{}, err
129 } 125 }
130 } 126 }
131 127
132 // type assertion 128 // type assertion
133 claims, ok := token.Claims.(*TokenClaims) 129 claims, ok := token.Claims.(*TokenClaims)
134 if !ok { 130 if !ok {
135 return TokenClaims{}, errors.New("token is not valid") 131 return TokenClaims{}, errors.New("token is not valid")
136 } 132 }
137 133
138 // extend token expiration date 134 // extend token expiration date
139 return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID}) 135 return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID})
140 } 136 }
141 137
142 // RbacCheck returns true if user that made HTTP request is authorized to 138 // AuthCheck returns JWT claims and boolean result of a check if req contains any role from roles.
143 // access the resource it is targeting. 139 // It checks if role extracted from reqest's Authorization header (JWT claims) matches any of
144 // It exctracts user's role from the JWT token located in Authorization header of 140 // provided comma-separated roles in roles. If roles is empty string check is skipped,
145 // http.Request and then compares it with the list of supplied roles and returns 141 // otherwise role is extracted from token claims and compared against roles.
146 // true if there's a match, if "*" is provided or if the authRoles is nil. 142 // If roles is "*" the check is automatically validated.
147 // Otherwise it returns false. 143 func AuthCheck(req *http.Request, roles string) (*TokenClaims, bool) {
148 func RbacCheck(req *http.Request, authRoles []string) bool { 144 if roles == "" {
149 if authRoles == nil {
150 return true
151 }
152
153 // validate token and check expiration date
154 claims, err := GetTokenClaims(req)
155 if err != nil {
156 return false
157 }
158 // check if token has expired
159 if claims.ExpiresAt < (time.Now()).Unix() {
160 return false
161 }
162
163 // check if role extracted from token matches
164 // any of the provided (allowed) ones
165 for _, r := range authRoles {
166 if claims.Role == r || r == "*" {
167 return true
168 }
169 }
170
171 return false
172 }
173
174 // AuthCheck returns token claims and boolean value based on user's rights to access resource specified in req.
175 // It exctracts user's role from the JWT token located in Authorization header of
176 // HTTP request and then compares it with the list of supplied (authorized);
177 // it returns true if there's a match, if "*" is provided or if the authRoles is nil.
178 func AuthCheck(req *http.Request, authRoles []string) (*TokenClaims, bool) {
179 if authRoles == nil {
180 return nil, true 145 return nil, true
181 } 146 }
182 147
183 // validate token and check expiration date 148 // validate token and check expiration date
184 claims, err := GetTokenClaims(req) 149 claims, err := GetTokenClaims(req)
185 if err != nil { 150 if err != nil {
186 return claims, false 151 return claims, false
187 } 152 }
188 // check if token has expired 153 // check if token has expired
189 if claims.ExpiresAt < (time.Now()).Unix() { 154 if claims.ExpiresAt < (time.Now()).Unix() {
190 return claims, false 155 return claims, false
191 } 156 }
192 157
193 // check if role extracted from token matches 158 if roles == "*" {
194 // any of the provided (allowed) ones 159 return claims, true
195 for _, r := range authRoles { 160 }
196 if claims.Role == r || r == "*" { 161
162 parts := strings.Split(roles, ",")
163 for i, _ := range parts {
164 r := strings.Trim(parts[i], " ")
165 if claims.Role == r {
197 return claims, true 166 return claims, true
198 } 167 }
199 } 168 }
200 169
201 return claims, false 170 return claims, false
202 } 171 }
203 172
204 // GetTokenClaims extracts JWT claims from Authorization header of the request. 173 // GetTokenClaims extracts JWT claims from Authorization header of req.
205 // Returns token claims or an error. 174 // Returns token claims or an error.
206 func GetTokenClaims(req *http.Request) (*TokenClaims, error) { 175 func GetTokenClaims(req *http.Request) (*TokenClaims, error) {
207 // check for and strip 'Bearer' prefix 176 // check for and strip 'Bearer' prefix
208 var tokstr string 177 var tokstr string
209 authHead := req.Header.Get("Authorization") 178 authHead := req.Header.Get("Authorization")
210 if ok := strings.HasPrefix(authHead, "Bearer "); ok { 179 if ok := strings.HasPrefix(authHead, "Bearer "); ok {
211 tokstr = strings.TrimPrefix(authHead, "Bearer ") 180 tokstr = strings.TrimPrefix(authHead, "Bearer ")
212 } else { 181 } else {
213 return &TokenClaims{}, errors.New("authorization header in incomplete") 182 return &TokenClaims{}, errors.New("authorization header in incomplete")
214 } 183 }
215 184
216 token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc) 185 token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc)
217 if err != nil { 186 if err != nil {
218 return &TokenClaims{}, err 187 return &TokenClaims{}, err
219 } 188 }
220 189
221 // type assertion 190 // type assertion
222 claims, ok := token.Claims.(*TokenClaims) 191 claims, ok := token.Claims.(*TokenClaims)
223 if !ok || !token.Valid { 192 if !ok || !token.Valid {
224 return &TokenClaims{}, errors.New("token is not valid") 193 return &TokenClaims{}, errors.New("token is not valid")
225 } 194 }
226 195
227 return claims, nil 196 return claims, nil
228 } 197 }
229 198
230 // randomSalt returns a string of random characters of 'saltSize' length. 199 // randomSalt returns a string of 32 random characters.
200 const saltSize = 32
201
231 func randomSalt() (s string, err error) { 202 func randomSalt() (s string, err error) {
232 rawsalt := make([]byte, saltSize) 203 rawsalt := make([]byte, saltSize)
233 204
234 _, err = rand.Read(rawsalt) 205 _, err = rand.Read(rawsalt)
235 if err != nil { 206 if err != nil {
236 return "", err 207 return "", err
237 } 208 }
238 209
239 s = hex.EncodeToString(rawsalt) 210 s = hex.EncodeToString(rawsalt)
240 return s, nil 211 return s, nil
241 } 212 }
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "encoding/json" 4 "encoding/json"
5 "fmt" 5 "fmt"
6 "net/http" 6 "net/http"
7 ) 7 )
8 8
9 type webError struct {
10 Request string `json:"request"`
11 Error string `json:"error"`
12 }
13
14 // NotFoundHandlerFunc writes HTTP error 404 to w. 9 // NotFoundHandlerFunc writes HTTP error 404 to w.
15 func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) { 10 func NotFoundHandlerFunc(w http.ResponseWriter, req *http.Request) {
16 SetDefaultHeaders(w) 11 SetDefaultHeaders(w)
17 if req.Method == "OPTIONS" { 12 if req.Method == "OPTIONS" {
18 return 13 return
19 } 14 }
20 NotFound(w, req, fmt.Sprintf("Resource you requested was not found: %s", req.URL.String())) 15 NotFound(w, req, fmt.Sprintf("Resource you requested was not found: %s", req.URL.String()))
21 } 16 }
22 17
23 // SetDefaultHeaders set's default headers for an HTTP response. 18 // SetDefaultHeaders set's default headers for an HTTP response.
24 func SetDefaultHeaders(w http.ResponseWriter) { 19 func SetDefaultHeaders(w http.ResponseWriter) {
25 w.Header().Set("Access-Control-Allow-Origin", "*") 20 w.Header().Set("Access-Control-Allow-Origin", "*")
26 w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") 21 w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS")
27 w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) 22 w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`)
28 w.Header().Set("Content-Type", "application/json; charset=utf-8") 23 w.Header().Set("Content-Type", "application/json; charset=utf-8")
29 } 24 }
30 25
31 func ReqLocale(req *http.Request, dflt string) string { 26 func ReqLocale(req *http.Request, dflt string) string {
32 loc := req.FormValue("locale") 27 loc := req.FormValue("locale")
33 if loc == "" { 28 if loc == "" {
34 return dflt 29 return dflt
35 } 30 }
36 return loc 31 return loc
37 } 32 }
38 33
39 // 2xx 34 // 2xx
40 func Success(w http.ResponseWriter, payload interface{}, code int) { 35 func Success(w http.ResponseWriter, payload interface{}, code int) {
41 w.WriteHeader(code) 36 w.WriteHeader(code)
42 if payload != nil { 37 if payload != nil {
43 json.NewEncoder(w).Encode(payload) 38 json.NewEncoder(w).Encode(payload)
44 } 39 }
45 } 40 }
46 41
47 // 200 42 // 200
48 func OK(w http.ResponseWriter, payload interface{}) { 43 func OK(w http.ResponseWriter, payload interface{}) {
49 Success(w, payload, http.StatusOK) 44 Success(w, payload, http.StatusOK)
50 } 45 }
51 46
52 // 201 47 // 201
53 func Created(w http.ResponseWriter, payload interface{}) { 48 func Created(w http.ResponseWriter, payload interface{}) {
54 Success(w, payload, http.StatusCreated) 49 Success(w, payload, http.StatusCreated)
55 } 50 }
56 51
52 type weberror struct {
53 Request string `json:"request"`
54 Error string `json:"error"`
55 }
56
57 // 4xx; 5xx 57 // 4xx; 5xx
58 func Error(w http.ResponseWriter, r *http.Request, code int, err string) { 58 func Error(w http.ResponseWriter, r *http.Request, code int, err string) {
59 werr := webError{Error: err, Request: r.Method + " " + r.RequestURI} 59 werr := weberror{Error: err, Request: r.Method + " " + r.RequestURI}
60 w.WriteHeader(code) 60 w.WriteHeader(code)
61 json.NewEncoder(w).Encode(werr) 61 json.NewEncoder(w).Encode(werr)
62 } 62 }
63 63
64 // 400 64 // 400
65 func BadRequest(w http.ResponseWriter, r *http.Request, err string) { 65 func BadRequest(w http.ResponseWriter, r *http.Request, err string) {
66 Error(w, r, http.StatusBadRequest, err) 66 Error(w, r, http.StatusBadRequest, err)
67 } 67 }
68 68
69 // 401 69 // 401
70 func Unauthorized(w http.ResponseWriter, r *http.Request, err string) { 70 func Unauthorized(w http.ResponseWriter, r *http.Request, err string) {
71 Error(w, r, http.StatusUnauthorized, err) 71 Error(w, r, http.StatusUnauthorized, err)
72 } 72 }
73 73
74 // 403 74 // 403
75 func Forbidden(w http.ResponseWriter, r *http.Request, err string) { 75 func Forbidden(w http.ResponseWriter, r *http.Request, err string) {
76 Error(w, r, http.StatusForbidden, err) 76 Error(w, r, http.StatusForbidden, err)
77 } 77 }
78 78
79 // 404 79 // 404
80 func NotFound(w http.ResponseWriter, r *http.Request, err string) { 80 func NotFound(w http.ResponseWriter, r *http.Request, err string) {
81 Error(w, r, http.StatusNotFound, err) 81 Error(w, r, http.StatusNotFound, err)
82 } 82 }
83 83
84 // 409 84 // 409
85 func Conflict(w http.ResponseWriter, r *http.Request, err string) { 85 func Conflict(w http.ResponseWriter, r *http.Request, err string) {
86 Error(w, r, http.StatusConflict, err) 86 Error(w, r, http.StatusConflict, err)
87 } 87 }
88 88
89 // 500 89 // 500
90 func InternalServerError(w http.ResponseWriter, r *http.Request, err string) { 90 func InternalServerError(w http.ResponseWriter, r *http.Request, err string) {
91 Error(w, r, http.StatusInternalServerError, err) 91 Error(w, r, http.StatusInternalServerError, err)
92 } 92 }
93
94 ///
95 /// Old API
96 ///
97
98 const (
99 templateHttpErr500_EN = "An internal server error has occurred."
100 templateHttpErr500_RS = "Došlo je do greške na serveru."
101 templateHttpErr400_EN = "Bad request."
102 templateHttpErr400_RS = "Neispravan zahtev."
103 templateHttpErr404_EN = "Resource not found."
104 templateHttpErr404_RS = "Resurs nije pronadjen."
105 templateHttpErr401_EN = "Unauthorized request."
106 templateHttpErr401_RS = "Neautorizovan zahtev."
107 )
108
109 type httpError struct {
110 Error []HttpErrorDesc `json:"error"`
111 Request string `json:"request"`
112 }
113
114 type HttpErrorDesc struct {
115 Lang string `json:"lang"`
116 Desc string `json:"description"`
117 }
118
119 // DeliverPayload encodes payload as JSON to w.
120 func DeliverPayload(w http.ResponseWriter, payload Payload) {
121 // Don't write status OK in the headers here. Leave it up for the caller.
122 // E.g. Status 201.
123 json.NewEncoder(w).Encode(payload)
124 payload.Data = nil
125 }
126
127 // ErrorResponse writes HTTP error to w.
128 func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) {
129 err := httpError{desc, r.Method + " " + r.RequestURI}
130 w.WriteHeader(code)
131 json.NewEncoder(w).Encode(err)
132 }
133
134 // NotFoundResponse writes HTTP error 404 to w.
135 func NotFoundResponse(w http.ResponseWriter, req *http.Request) {
136 ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{
137 {"en", templateHttpErr404_EN},
138 {"rs", templateHttpErr404_RS},
139 })
140 }
141
142 // BadRequestResponse writes HTTP error 400 to w.
143 func BadRequestResponse(w http.ResponseWriter, req *http.Request) {
144 ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{
145 {"en", templateHttpErr400_EN},
146 {"rs", templateHttpErr400_RS},
147 })
148 }
149
150 // InternalSeverErrorResponse writes HTTP error 500 to w.
151 func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) {
152 ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{
153 {"en", templateHttpErr500_EN},
154 {"rs", templateHttpErr500_RS},
155 })
156 }
157
158 // UnauthorizedError writes HTTP error 401 to w.
159 func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) {
160 w.Header().Set("WWW-Authenticate", "Bearer")
161 ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "database/sql" 4 "database/sql"
5 "encoding/json" 5 "encoding/json"
6 "errors" 6 "errors"
7 "fmt" 7 "fmt"
8 "io" 8 "io"
9 "net/http" 9 "net/http"
10 "sync" 10 "sync"
11 "time" 11 "time"
12 12
13 "git.to-net.rs/marko.tikvic/gologger" 13 "git.to-net.rs/marko.tikvic/gologger"
14 ) 14 )
15 15
16 var ( 16 var (
17 mu = &sync.Mutex{} 17 mu = &sync.Mutex{}
18 metadata = make(map[string]Payload) 18 metadata = make(map[string]Payload)
19
19 updateQue = make(map[string][]byte) 20 updateQue = make(map[string][]byte)
20 21
21 metadataDB *sql.DB 22 metadataDB *sql.DB
22 activeProject string 23 activeProject string
23 24
24 inited bool 25 inited bool
25 driver string 26 driver string
26 logger *gologger.Logger 27 logger *gologger.Logger
27 ) 28 )
28 29
29 type LangMap map[string]map[string]string 30 type LangMap map[string]map[string]string
30 31
31 type Field struct { 32 type Field struct {
32 Parameter string `json:"param"` 33 Parameter string `json:"param"`
33 Type string `json:"type"` 34 Type string `json:"type"`
34 Visible bool `json:"visible"` 35 Visible bool `json:"visible"`
35 Editable bool `json:"editable"` 36 Editable bool `json:"editable"`
36 } 37 }
37 38
38 type CorrelationField struct { 39 type CorrelationField struct {
39 Result string `json:"result"` 40 Result string `json:"result"`
40 Elements []string `json:"elements"` 41 Elements []string `json:"elements"`
41 Type string `json:"type"` 42 Type string `json:"type"`
42 } 43 }
43 44
44 type Translation struct { 45 type Translation struct {
45 Language string `json:"language"` 46 Language string `json:"language"`
46 FieldsLabels map[string]string `json:"fieldsLabels"` 47 FieldsLabels map[string]string `json:"fieldsLabels"`
47 } 48 }
48 49
49 type Payload struct { 50 type Payload struct {
50 Method string `json:"method"` 51 Method string `json:"method"`
51 Params map[string]string `json:"params"` 52 Params map[string]string `json:"params"`
52 Lang []Translation `json:"lang"` 53 Lang []Translation `json:"lang"`
53 Fields []Field `json:"fields"` 54 Fields []Field `json:"fields"`
54 Correlations []CorrelationField `json:"correlationFields"` 55 Correlations []CorrelationField `json:"correlationFields"`
55 IdField string `json:"idField"` 56 IdField string `json:"idField"`
56 57
57 // Data holds JSON payload. 58 // Data holds JSON payload.
58 // It can't be used for itteration. 59 // It can't be used for itteration.
59 Data interface{} `json:"data"` 60 Data interface{} `json:"data"`
60 } 61 }
61 62
62 func (p *Payload) SetData(data interface{}) { 63 func (p *Payload) SetData(data interface{}) {
63 p.Data = data 64 p.Data = data
64 } 65 }
65 66
66 // NewPayload returs a payload sceleton for entity described with etype. 67 // NewPayload returs a payload sceleton for entity described with etype.
67 func NewPayload(r *http.Request, etype string) Payload { 68 func NewPayload(r *http.Request, etype string) Payload {
68 pload := metadata[etype] 69 pload := metadata[etype]
69 pload.Method = r.Method + " " + r.RequestURI 70 pload.Method = r.Method + " " + r.RequestURI
70 return pload 71 return pload
71 } 72 }
72 73
73 // DecodeJSON decodes JSON data from r to v. 74 // DecodeJSON decodes JSON data from r to v.
74 // Returns an error if it fails. 75 // Returns an error if it fails.
75 func DecodeJSON(r io.Reader, v interface{}) error { 76 func DecodeJSON(r io.Reader, v interface{}) error {
76 return json.NewDecoder(r).Decode(v) 77 return json.NewDecoder(r).Decode(v)
77 } 78 }
78 79
79 // InitPayloadsMetadata loads all payloads' information into 'metadata' variable. 80 // InitPayloadsMetadata loads all payloads' information into 'metadata' variable.
80 func InitPayloadsMetadata(drv string, db *sql.DB, project string) error { 81 func InitPayloadsMetadata(drv string, db *sql.DB, project string) error {
81 var err error 82 var err error
82 if drv != "ora" && drv != "mysql" { 83 if drv != "ora" && drv != "mysql" {
83 err = errors.New("driver not supported") 84 err = errors.New("driver not supported")
84 return err 85 return err
85 } 86 }
86 87
87 driver = drv 88 driver = drv
88 metadataDB = db 89 metadataDB = db
89 activeProject = project 90 activeProject = project
90 91
91 logger, err = gologger.New("metadata", gologger.MaxLogSize100KB) 92 logger, err = gologger.New("metadata", gologger.MaxLogSize100KB)
92 if err != nil { 93 if err != nil {
93 fmt.Printf("webutility: %s\n", err.Error()) 94 fmt.Printf("webutility: %s\n", err.Error())
94 } 95 }
95 96
96 mu.Lock() 97 mu.Lock()
97 defer mu.Unlock() 98 defer mu.Unlock()
98 err = initMetadata(project) 99 err = initMetadata(project)
99 if err != nil { 100 if err != nil {
100 return err 101 return err
101 } 102 }
102 inited = true 103 inited = true
103 104
104 return nil 105 return nil
105 } 106 }
106 107
107 func EnableHotloading(interval int) { 108 func EnableHotloading(interval int) {
108 if interval > 0 { 109 if interval > 0 {
109 go hotload(interval) 110 go hotload(interval)
110 } 111 }
111 } 112 }
112 113
113 func GetMetadataForAllEntities() map[string]Payload { 114 func GetMetadataForAllEntities() map[string]Payload {
114 return metadata 115 return metadata
115 } 116 }
116 117
117 func GetMetadataForEntity(t string) (Payload, bool) { 118 func GetMetadataForEntity(t string) (Payload, bool) {
118 p, ok := metadata[t] 119 p, ok := metadata[t]
119 return p, ok 120 return p, ok
120 } 121 }
121 122
122 func QueEntityModelUpdate(entityType string, v interface{}) { 123 func QueEntityModelUpdate(entityType string, v interface{}) {
123 updateQue[entityType], _ = json.Marshal(v) 124 updateQue[entityType], _ = json.Marshal(v)
124 } 125 }
125 126
126 func UpdateEntityModels(command string) (total, upd, add int, err error) { 127 func UpdateEntityModels(command string) (total, upd, add int, err error) {
127 if command != "force" && command != "missing" { 128 if command != "force" && command != "missing" {
128 return total, 0, 0, errors.New("webutility: unknown command: " + command) 129 return total, 0, 0, errors.New("webutility: unknown command: " + command)
129 } 130 }
130 131
131 if !inited { 132 if !inited {
132 return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried.") 133 return 0, 0, 0, errors.New("webutility: metadata not initialized but update was tried.")
133 } 134 }
134 135
135 total = len(updateQue) 136 total = len(updateQue)
136 137
137 toUpdate := make([]string, 0) 138 toUpdate := make([]string, 0)
138 toAdd := make([]string, 0) 139 toAdd := make([]string, 0)
139 140
140 for k, _ := range updateQue { 141 for k, _ := range updateQue {
141 if _, exists := metadata[k]; exists { 142 if _, exists := metadata[k]; exists {
142 if command == "force" { 143 if command == "force" {
143 toUpdate = append(toUpdate, k) 144 toUpdate = append(toUpdate, k)
144 } 145 }
145 } else { 146 } else {
146 toAdd = append(toAdd, k) 147 toAdd = append(toAdd, k)
147 } 148 }
148 } 149 }
149 150
150 var uStmt *sql.Stmt 151 var uStmt *sql.Stmt
151 if driver == "ora" { 152 if driver == "ora" {
152 uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2") 153 uStmt, err = metadataDB.Prepare("update entities set entity_model = :1 where entity_type = :2")
153 if err != nil { 154 if err != nil {
154 return 155 return
155 } 156 }
156 } else if driver == "mysql" { 157 } else if driver == "mysql" {
157 uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?") 158 uStmt, err = metadataDB.Prepare("update entities set entity_model = ? where entity_type = ?")
158 if err != nil { 159 if err != nil {
159 return 160 return
160 } 161 }
161 } 162 }
162 for _, k := range toUpdate { 163 for _, k := range toUpdate {
163 _, err = uStmt.Exec(string(updateQue[k]), k) 164 _, err = uStmt.Exec(string(updateQue[k]), k)
164 if err != nil { 165 if err != nil {
165 return 166 return
166 } 167 }
167 upd++ 168 upd++
168 } 169 }
169 170
170 blankPayload, _ := json.Marshal(Payload{}) 171 blankPayload, _ := json.Marshal(Payload{})
171 var iStmt *sql.Stmt 172 var iStmt *sql.Stmt
172 if driver == "ora" { 173 if driver == "ora" {
173 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)") 174 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(:1, :2, :3, :4)")
174 if err != nil { 175 if err != nil {
175 return 176 return
176 } 177 }
177 } else if driver == "mysql" { 178 } else if driver == "mysql" {
178 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)") 179 iStmt, err = metadataDB.Prepare("insert into entities(projekat, metadata, entity_type, entity_model) values(?, ?, ?, ?)")
179 if err != nil { 180 if err != nil {
180 return 181 return
181 } 182 }
182 } 183 }
183 for _, k := range toAdd { 184 for _, k := range toAdd {
184 _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k])) 185 _, err = iStmt.Exec(activeProject, string(blankPayload), k, string(updateQue[k]))
185 if err != nil { 186 if err != nil {
186 return 187 return
187 } 188 }
188 metadata[k] = Payload{} 189 metadata[k] = Payload{}
189 add++ 190 add++
190 } 191 }
191 192
192 return total, upd, add, nil 193 return total, upd, add, nil
193 } 194 }
194 195
195 func initMetadata(project string) error { 196 func initMetadata(project string) error {
196 rows, err := metadataDB.Query(`select 197 rows, err := metadataDB.Query(`select
197 entity_type, 198 entity_type,
198 metadata 199 metadata
199 from entities 200 from entities
200 where projekat = ` + fmt.Sprintf("'%s'", project)) 201 where projekat = ` + fmt.Sprintf("'%s'", project))
201 if err != nil { 202 if err != nil {
202 return err 203 return err
203 } 204 }
204 defer rows.Close() 205 defer rows.Close()
205 206
206 if len(metadata) > 0 { 207 if len(metadata) > 0 {
207 metadata = nil 208 metadata = nil
208 } 209 }
209 metadata = make(map[string]Payload) 210 metadata = make(map[string]Payload)
210 for rows.Next() { 211 for rows.Next() {
211 var name, load string 212 var name, load string
212 rows.Scan(&name, &load) 213 rows.Scan(&name, &load)
213 214
214 p := Payload{} 215 p := Payload{}
215 err := json.Unmarshal([]byte(load), &p) 216 err := json.Unmarshal([]byte(load), &p)
216 if err != nil { 217 if err != nil {
217 logger.Log("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load) 218 logger.Log("webutility: couldn't init: '%s' metadata: %s:\n%s\n", name, err.Error(), load)
218 } else { 219 } else {
219 metadata[name] = p 220 metadata[name] = p
220 } 221 }
221 } 222 }
222 223
223 return nil 224 return nil
224 } 225 }
225 226
226 func hotload(n int) { 227 func hotload(n int) {
227 entityScan := make(map[string]int64) 228 entityScan := make(map[string]int64)
228 firstCheck := true 229 firstCheck := true
229 for { 230 for {
230 time.Sleep(time.Duration(n) * time.Second) 231 time.Sleep(time.Duration(n) * time.Second)
231 rows, err := metadataDB.Query(`select 232 rows, err := metadataDB.Query(`select
232 ora_rowscn, 233 ora_rowscn,
233 entity_type 234 entity_type
234 from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject)) 235 from entities where projekat = ` + fmt.Sprintf("'%s'", activeProject))
235 if err != nil { 236 if err != nil {
236 logger.Log("webutility: hotload failed: %v\n", err) 237 logger.Log("webutility: hotload failed: %v\n", err)
237 time.Sleep(time.Duration(n) * time.Second) 238 time.Sleep(time.Duration(n) * time.Second)
238 continue 239 continue
239 } 240 }
240 241
241 var toRefresh []string 242 var toRefresh []string
242 for rows.Next() { 243 for rows.Next() {
243 var scanID int64 244 var scanID int64
244 var entity string 245 var entity string
245 rows.Scan(&scanID, &entity) 246 rows.Scan(&scanID, &entity)
246 oldID, ok := entityScan[entity] 247 oldID, ok := entityScan[entity]
247 if !ok || oldID != scanID { 248 if !ok || oldID != scanID {
248 entityScan[entity] = scanID 249 entityScan[entity] = scanID
249 toRefresh = append(toRefresh, entity) 250 toRefresh = append(toRefresh, entity)
250 } 251 }
251 } 252 }
252 rows.Close() 253 rows.Close()
253 254
254 if rows.Err() != nil { 255 if rows.Err() != nil {
255 logger.Log("webutility: hotload rset error: %v\n", rows.Err()) 256 logger.Log("webutility: hotload rset error: %v\n", rows.Err())
256 time.Sleep(time.Duration(n) * time.Second) 257 time.Sleep(time.Duration(n) * time.Second)
257 continue 258 continue
258 } 259 }
259 260
260 if len(toRefresh) > 0 && !firstCheck { 261 if len(toRefresh) > 0 && !firstCheck {
261 mu.Lock() 262 mu.Lock()
262 refreshMetadata(toRefresh) 263 refreshMetadata(toRefresh)
263 mu.Unlock() 264 mu.Unlock()
264 } 265 }
265 if firstCheck { 266 if firstCheck {
266 firstCheck = false 267 firstCheck = false
267 } 268 }
268 } 269 }
269 } 270 }
270 271
271 func refreshMetadata(entities []string) { 272 func refreshMetadata(entities []string) {
272 for _, e := range entities { 273 for _, e := range entities {
273 fmt.Printf("refreshing %s\n", e) 274 fmt.Printf("refreshing %s\n", e)
274 rows, err := metadataDB.Query(`select 275 rows, err := metadataDB.Query(`select
275 metadata 276 metadata
276 from entities 277 from entities
277 where projekat = ` + fmt.Sprintf("'%s'", activeProject) + 278 where projekat = ` + fmt.Sprintf("'%s'", activeProject) +
278 ` and entity_type = ` + fmt.Sprintf("'%s'", e)) 279 ` and entity_type = ` + fmt.Sprintf("'%s'", e))
279 280
280 if err != nil { 281 if err != nil {
281 logger.Log("webutility: refresh: prep: %v\n", err) 282 logger.Log("webutility: refresh: prep: %v\n", err)
282 rows.Close() 283 rows.Close()
283 continue 284 continue
284 } 285 }
285 286
286 for rows.Next() { 287 for rows.Next() {
287 var load string 288 var load string
288 rows.Scan(&load) 289 rows.Scan(&load)
289 p := Payload{} 290 p := Payload{}
290 err := json.Unmarshal([]byte(load), &p) 291 err := json.Unmarshal([]byte(load), &p)
291 if err != nil { 292 if err != nil {
292 logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load) 293 logger.Log("webutility: couldn't refresh: '%s' metadata: %s\n%s\n", e, err.Error(), load)
293 } else { 294 } else {
294 metadata[e] = p 295 metadata[e] = p
295 } 296 }
296 } 297 }
297 rows.Close() 298 rows.Close()
298 } 299 }
299 } 300 }
300 301
301 /* 302 /*
302 func ModifyMetadataForEntity(entityType string, p *Payload) error { 303 func ModifyMetadataForEntity(entityType string, p *Payload) error {
303 md, err := json.Marshal(*p) 304 md, err := json.Marshal(*p)
304 if err != nil { 305 if err != nil {
305 return err 306 return err
306 } 307 }
307 308
308 mu.Lock() 309 mu.Lock()
309 defer mu.Unlock() 310 defer mu.Unlock()
310 _, err = metadataDB.PrepAndExe(`update entities set 311 _, err = metadataDB.PrepAndExe(`update entities set
311 metadata = :1 312 metadata = :1
312 where projekat = :2 313 where projekat = :2
313 and entity_type = :3`, 314 and entity_type = :3`,
314 string(md), 315 string(md),
315 activeProject, 316 activeProject,
316 entityType) 317 entityType)
317 if err != nil { 318 if err != nil {
318 return err 319 return err
319 } 320 }
320 return nil 321 return nil
321 } 322 }
322 323
323 func DeleteEntityModel(entityType string) error { 324 func DeleteEntityModel(entityType string) error {
324 _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType) 325 _, err := metadataDB.PrepAndExe("delete from entities where entity_type = :1", entityType)
325 if err == nil { 326 if err == nil {
326 mu.Lock() 327 mu.Lock()
327 delete(metadata, entityType) 328 delete(metadata, entityType)
328 mu.Unlock() 329 mu.Unlock()
329 } 330 }
330 return err 331 return err
331 } 332 }
332 */ 333 */
333 334
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "encoding/json" 4 "encoding/json"
5 "errors" 5 "errors"
6 "io/ioutil" 6 "io/ioutil"
7 "sync"
7 ) 8 )
8 9
9 type Dictionary struct { 10 type Dictionary struct {
11 my sync.Mutex
10 locales map[string]map[string]string 12 locales map[string]map[string]string
11 supported []string 13 supported []string
12 defaultLocale string 14 defaultLocale string
13 } 15 }
14 16
15 func NewDictionary() *Dictionary { 17 func NewDictionary() *Dictionary {
16 return &Dictionary{ 18 return &Dictionary{
17 locales: map[string]map[string]string{}, 19 locales: map[string]map[string]string{},
18 } 20 }
19 } 21 }
20 22
21 func (d *Dictionary) AddLocale(loc, filePath string) error { 23 func (d *Dictionary) AddLocale(loc, filePath string) error {
22 file, err := ioutil.ReadFile(filePath) 24 file, err := ioutil.ReadFile(filePath)
23 if err != nil { 25 if err != nil {
24 return err 26 return err
25 } 27 }
26 28
27 var data interface{} 29 var data interface{}
28 err = json.Unmarshal(file, &data) 30 err = json.Unmarshal(file, &data)
29 if err != nil { 31 if err != nil {
30 return err 32 return err
31 } 33 }
32 34
33 l := map[string]string{} 35 l := map[string]string{}
34 for k, v := range data.(map[string]interface{}) { 36 for k, v := range data.(map[string]interface{}) {
35 l[k] = v.(string) 37 l[k] = v.(string)
36 } 38 }
39
40 mu.Lock()
41 defer mu.Unlock()
37 d.locales[loc] = l 42 d.locales[loc] = l
38 d.supported = append(d.supported, loc) 43 d.supported = append(d.supported, loc)
39 44
40 return nil 45 return nil
41 } 46 }
42 47
43 func (d *Dictionary) Translate(loc, key string) string { 48 func (d *Dictionary) Translate(loc, key string) string {
44 return d.locales[loc][key] 49 return d.locales[loc][key]
45 } 50 }
46 51
47 func (d *Dictionary) HasLocale(loc string) bool { 52 func (d *Dictionary) HasLocale(loc string) bool {
48 for _, v := range d.supported { 53 for _, v := range d.supported {
49 if v == loc { 54 if v == loc {
50 return true 55 return true
51 } 56 }
52 } 57 }
53 return false 58 return false
54 } 59 }
55 60
56 func (d *Dictionary) SetDefaultLocale(loc string) error { 61 func (d *Dictionary) SetDefaultLocale(loc string) error {
57 if !d.HasLocale(loc) { 62 if !d.HasLocale(loc) {
58 return errors.New("dictionary does not contain translations for " + loc) 63 return errors.New("dictionary does not contain translations for " + loc)
59 } 64 }
60 d.defaultLocale = loc 65 d.defaultLocale = loc
61 return nil 66 return nil
62 } 67 }
63 68
64 func (d *Dictionary) GetDefaultLocale() string { 69 func (d *Dictionary) GetDefaultLocale() string {
65 return d.defaultLocale 70 return d.defaultLocale
66 } 71 }
67 72
1 package webutility 1 package webutility
2 2
3 import ( 3 import (
4 "net/http" 4 "net/http"
5 "time" 5 "time"
6 6
7 "git.to-net.rs/marko.tikvic/gologger" 7 "git.to-net.rs/marko.tikvic/gologger"
8 ) 8 )
9 9
10 var reqLogger *gologger.Logger 10 func SetHeaders(h http.HandlerFunc) http.HandlerFunc {
11
12 func WithSetHeaders(h http.HandlerFunc) http.HandlerFunc {
13 return func(w http.ResponseWriter, req *http.Request) { 11 return func(w http.ResponseWriter, req *http.Request) {
14 SetDefaultHeaders(w) 12 SetDefaultHeaders(w)
15 if req.Method == http.MethodOptions { 13 if req.Method == http.MethodOptions {
16 return 14 return
17 } 15 }
18 h(w, req) 16 h(w, req)
19 } 17 }
20 } 18 }
21 19
22 func WithParseForm(h http.HandlerFunc) http.HandlerFunc { 20 func ParseForm(h http.HandlerFunc) http.HandlerFunc {
23 return func(w http.ResponseWriter, req *http.Request) { 21 return func(w http.ResponseWriter, req *http.Request) {
24 err := req.ParseForm() 22 err := req.ParseForm()
25 if err != nil { 23 if err != nil {
26 BadRequest(w, req, err.Error()) 24 BadRequest(w, req, err.Error())
27 return 25 return
28 } 26 }
29 h(w, req) 27 h(w, req)
30 } 28 }
31 } 29 }
32 30
31 var reqLogger *gologger.Logger
32
33 func EnableLogging(log string) error { 33 func EnableLogging(log string) error {
34 var err error 34 var err error
35 reqLogger, err = gologger.New(log, gologger.MaxLogSize5MB) 35 reqLogger, err = gologger.New(log, gologger.MaxLogSize5MB)
36 return err 36 return err
37 } 37 }
38 38
39 func WithLog(h http.HandlerFunc) http.HandlerFunc { 39 func Log(h http.HandlerFunc) http.HandlerFunc {
40 return func(w http.ResponseWriter, req *http.Request) { 40 return func(w http.ResponseWriter, req *http.Request) {
41 reqLogger.LogRequest(req, "") 41 reqLogger.LogRequest(req, "")
42 t1 := time.Now() 42 t1 := time.Now()
43 h(w, req) 43 h(w, req)
44 t2 := time.Now() 44 t2 := time.Now()
45 reqLogger.LogResponse(w, t2.Sub(t1)) 45 reqLogger.LogResponse(w, t2.Sub(t1))
46 } 46 }
47 } 47 }
48 48
49 func WithAuth(authorizedRoles []string, h http.HandlerFunc) http.HandlerFunc { 49 func Auth(roles string, h http.HandlerFunc) http.HandlerFunc {
50 return func(w http.ResponseWriter, req *http.Request) { 50 return func(w http.ResponseWriter, req *http.Request) {
51 if _, ok := AuthCheck(req, authorizedRoles); !ok { 51 if _, ok := AuthCheck(req, roles); !ok {
52 Unauthorized(w, req, "") 52 Unauthorized(w, req, "")
53 return 53 return
54 } 54 }
55 h(w, req) 55 h(w, req)
56 } 56 }