Commit f84e7607d3c434e8e06cc329600d4a0df6ae7e96

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

added dictionary; improved http utility; json_utility now supports oracle and mysql drivers

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