Commit d2ddf82ef16c3ab5764f1b8136f1f387b117277b
1 parent
6b16c88f25
Exists in
master
and in
1 other branch
started on new rbac
Showing
4 changed files
with
131 additions
and
93 deletions
Show diff stats
auth_utility.go
1 | // TODO: Improve roles | ||
1 | package webutility | 2 | package webutility |
2 | 3 | ||
3 | import ( | 4 | import ( |
4 | "errors" | ||
5 | "time" | ||
6 | "crypto/sha256" | ||
7 | "crypto/rand" | 5 | "crypto/rand" |
6 | "crypto/sha256" | ||
8 | "encoding/hex" | 7 | "encoding/hex" |
9 | "strings" | 8 | "errors" |
10 | "net/http" | 9 | "net/http" |
10 | "strings" | ||
11 | "time" | ||
11 | 12 | ||
12 | "github.com/dgrijalva/jwt-go" | 13 | "github.com/dgrijalva/jwt-go" |
13 | ) | 14 | ) |
14 | 15 | ||
15 | const OneDay = time.Hour*24 | 16 | const OneDay = time.Hour * 24 |
16 | const OneWeek = OneDay*7 | 17 | const OneWeek = OneDay * 7 |
17 | const saltSize = 32 | 18 | const saltSize = 32 |
18 | const appName = "korisnicki-centar" | 19 | const appName = "korisnicki-centar" |
19 | const secret = "korisnicki-centar-api" | 20 | const secret = "korisnicki-centar-api" |
20 | 21 | ||
21 | const RoleAdmin string = "ADMINISTRATOR" | 22 | const RoleAdmin string = "ADMINISTRATOR" |
22 | const RoleManager string = "RUKOVODILAC" | 23 | const RoleManager string = "RUKOVODILAC" |
23 | const RoleReporter string = "REPORTER" | 24 | const RoleReporter string = "REPORTER" |
24 | const RoleOperator string = "OPERATER" | 25 | const RoleOperator string = "OPERATER" |
25 | const RoleAdminID uint32 = 1 | 26 | const RoleAdminID uint32 = 1 |
26 | const RoleManagerID uint32 = 2 | 27 | const RoleManagerID uint32 = 2 |
27 | const RoleReporterID uint32 = 3 | 28 | const RoleReporterID uint32 = 3 |
28 | const RoleOperatorID uint32 = 4 | 29 | const RoleOperatorID uint32 = 4 |
29 | 30 | ||
31 | type Role struct { | ||
32 | name string | ||
33 | id uint32 | ||
34 | } | ||
35 | |||
30 | // TokenClaims are JWT token claims. | 36 | // TokenClaims are JWT token claims. |
31 | type TokenClaims struct { | 37 | type TokenClaims struct { |
32 | Username string `json:"username"` | 38 | Username string `json:"username"` |
33 | Role string `json:"role"` | 39 | Role string `json:"role"` |
34 | RoleID uint32 `json:"roleID"` | 40 | RoleID uint32 `json:"roleID"` |
35 | jwt.StandardClaims | 41 | jwt.StandardClaims |
36 | } | 42 | } |
37 | 43 | ||
38 | // CredentialsStruct is an instace of username/password values. | 44 | // CredentialsStruct is an instace of username/password values. |
39 | type CredentialsStruct struct { | 45 | type CredentialsStruct struct { |
40 | Username string `json:"username"` | 46 | Username string `json:"username"` |
41 | Password string `json:"password"` | 47 | Password string `json:"password"` |
42 | } | 48 | } |
43 | 49 | ||
50 | var admin Role = Role{RoleAdmin, RoleAdminID} | ||
51 | var manager Role = Role{RoleManager, RoleManagerID} | ||
52 | var reporter Role = Role{RoleReporter, RoleReporterID} | ||
53 | var operator Role = Role{RoleOperator, RoleOperatorID} | ||
54 | |||
44 | // generateSalt returns a string of random characters of 'saltSize' length. | 55 | // generateSalt returns a string of random characters of 'saltSize' length. |
45 | func generateSalt() (salt string, err error) { | 56 | func generateSalt() (salt string, err error) { |
46 | rawsalt := make([]byte, saltSize) | 57 | rawsalt := make([]byte, saltSize) |
47 | 58 | ||
48 | _, err = rand.Read(rawsalt) | 59 | _, err = rand.Read(rawsalt) |
49 | if err != nil { | 60 | if err != nil { |
50 | return "", err | 61 | return "", err |
51 | } | 62 | } |
52 | 63 | ||
53 | salt = hex.EncodeToString(rawsalt) | 64 | salt = hex.EncodeToString(rawsalt) |
54 | return salt, nil | 65 | return salt, nil |
55 | } | 66 | } |
56 | 67 | ||
57 | // HashString hashes input string with SHA256 algorithm. | 68 | // HashString hashes input string with SHA256 algorithm. |
58 | // If the presalt parameter is not provided HashString will generate new salt string. | 69 | // If the presalt parameter is not provided HashString will generate new salt string. |
59 | // Returns hash and salt string or an error if it fails. | 70 | // Returns hash and salt string or an error if it fails. |
60 | func HashString(str string, presalt string) (hash, salt string, err error) { | 71 | func HashString(str string, presalt string) (hash, salt string, err error) { |
61 | // chech if message is presalted | 72 | // chech if message is presalted |
62 | if presalt == "" { | 73 | if presalt == "" { |
63 | salt, err = generateSalt() | 74 | salt, err = generateSalt() |
64 | if err != nil { | 75 | if err != nil { |
65 | return "", "", err | 76 | return "", "", err |
66 | } | 77 | } |
67 | } else { | 78 | } else { |
68 | salt = presalt | 79 | salt = presalt |
69 | } | 80 | } |
70 | 81 | ||
71 | // convert strings to raw byte slices | 82 | // convert strings to raw byte slices |
72 | rawstr := []byte(str) | 83 | rawstr := []byte(str) |
73 | rawsalt, err := hex.DecodeString(salt) | 84 | rawsalt, err := hex.DecodeString(salt) |
74 | if err != nil { | 85 | if err != nil { |
75 | return "", "", err | 86 | return "", "", err |
76 | } | 87 | } |
77 | 88 | ||
78 | rawdata := make([]byte, len(rawstr) + len(rawsalt)) | 89 | rawdata := make([]byte, len(rawstr)+len(rawsalt)) |
79 | rawdata = append(rawdata, rawstr...) | 90 | rawdata = append(rawdata, rawstr...) |
80 | rawdata = append(rawdata, rawsalt...) | 91 | rawdata = append(rawdata, rawsalt...) |
81 | 92 | ||
82 | // hash message + salt | 93 | // hash message + salt |
83 | hasher := sha256.New() | 94 | hasher := sha256.New() |
84 | hasher.Write(rawdata) | 95 | hasher.Write(rawdata) |
85 | rawhash := hasher.Sum(nil) | 96 | rawhash := hasher.Sum(nil) |
86 | 97 | ||
87 | hash = hex.EncodeToString(rawhash) | 98 | hash = hex.EncodeToString(rawhash) |
88 | return hash, salt, nil | 99 | return hash, salt, nil |
89 | } | 100 | } |
90 | 101 | ||
91 | // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims. | 102 | // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims. |
92 | // It returns an error if it fails. | 103 | // It returns an error if it fails. |
93 | func CreateAPIToken(username, role string, roleID uint32) (string, error) { | 104 | func CreateAPIToken(username, role string, roleID uint32) (string, error) { |
94 | var apiToken string | 105 | var apiToken string |
95 | var err error | 106 | var err error |
96 | 107 | ||
97 | if err != nil { | 108 | if err != nil { |
98 | return "", err | 109 | return "", err |
99 | } | 110 | } |
100 | 111 | ||
101 | claims := TokenClaims{ | 112 | claims := TokenClaims{ |
102 | username, | 113 | username, |
103 | role, | 114 | role, |
104 | roleID, | 115 | roleID, |
105 | jwt.StandardClaims{ | 116 | jwt.StandardClaims{ |
106 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), | 117 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), |
107 | Issuer: appName, | 118 | Issuer: appName, |
108 | }, | 119 | }, |
109 | } | 120 | } |
110 | 121 | ||
111 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 122 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
112 | apiToken, err = jwtToken.SignedString([]byte(secret)) | 123 | apiToken, err = jwtToken.SignedString([]byte(secret)) |
113 | if err != nil { | 124 | if err != nil { |
114 | return "", err | 125 | return "", err |
115 | } | 126 | } |
116 | return apiToken, nil | 127 | return apiToken, nil |
117 | } | 128 | } |
118 | 129 | ||
119 | // RefreshAPIToken prolongs JWT token's expiration date for one week. | 130 | // RefreshAPIToken prolongs JWT token's expiration date for one week. |
120 | // It returns new JWT token or an error if it fails. | 131 | // It returns new JWT token or an error if it fails. |
121 | func RefreshAPIToken(tokenString string) (string, error) { | 132 | func RefreshAPIToken(tokenString string) (string, error) { |
122 | var newToken string | 133 | var newToken string |
123 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 134 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
124 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) | 135 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) |
125 | if err != nil { | 136 | if err != nil { |
126 | return "", err | 137 | return "", err |
127 | } | 138 | } |
128 | 139 | ||
129 | // type assertion | 140 | // type assertion |
130 | claims, ok := token.Claims.(*TokenClaims) | 141 | claims, ok := token.Claims.(*TokenClaims) |
131 | if !ok || !token.Valid { | 142 | if !ok || !token.Valid { |
132 | return "", errors.New("token is not valid") | 143 | return "", errors.New("token is not valid") |
133 | } | 144 | } |
134 | 145 | ||
135 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() | 146 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() |
136 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 147 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
137 | 148 | ||
138 | newToken, err = jwtToken.SignedString([]byte(secret)) | 149 | newToken, err = jwtToken.SignedString([]byte(secret)) |
139 | if err != nil { | 150 | if err != nil { |
140 | return "", err | 151 | return "", err |
141 | } | 152 | } |
142 | 153 | ||
143 | return newToken, nil | 154 | return newToken, nil |
144 | } | 155 | } |
145 | 156 | ||
146 | // ParseAPIToken parses JWT token claims. | 157 | // ParseAPIToken parses JWT token claims. |
147 | // It returns a pointer to TokenClaims struct or an error if it fails. | 158 | // It returns a pointer to TokenClaims struct or an error if it fails. |
148 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { | 159 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { |
149 | if ok := strings.HasPrefix(tokenString, "Bearer "); ok { | 160 | if ok := strings.HasPrefix(tokenString, "Bearer "); ok { |
150 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 161 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
151 | } else { | 162 | } else { |
152 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | 163 | return &TokenClaims{}, errors.New("Authorization header is incomplete") |
153 | } | 164 | } |
154 | 165 | ||
155 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) | 166 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) |
156 | if err != nil { | 167 | if err != nil { |
157 | return &TokenClaims{}, err | 168 | return &TokenClaims{}, err |
158 | } | 169 | } |
159 | 170 | ||
160 | // type assertion | 171 | // type assertion |
161 | claims, ok := token.Claims.(*TokenClaims) | 172 | claims, ok := token.Claims.(*TokenClaims) |
162 | if !ok || !token.Valid { | 173 | if !ok || !token.Valid { |
163 | return &TokenClaims{}, errors.New("token is not valid") | 174 | return &TokenClaims{}, errors.New("token is not valid") |
164 | } | 175 | } |
165 | return claims, nil | 176 | return claims, nil |
166 | } | 177 | } |
167 | 178 | ||
168 | func GetTokenClaims(r *http.Request) (claims *TokenClaims, err error) { | 179 | func GetTokenClaims(r *http.Request) (claims *TokenClaims, err error) { |
169 | token := r.Header.Get("Authorization") | 180 | token := r.Header.Get("Authorization") |
170 | if ok := strings.HasPrefix(token, "Bearer "); ok { | 181 | if ok := strings.HasPrefix(token, "Bearer "); ok { |
171 | token = strings.TrimPrefix(token, "Bearer ") | 182 | token = strings.TrimPrefix(token, "Bearer ") |
172 | } else { | 183 | } else { |
173 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | 184 | return &TokenClaims{}, errors.New("Authorization header is incomplete") |
174 | } | 185 | } |
175 | 186 | ||
176 | parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretFunc) | 187 | parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretFunc) |
177 | if err != nil { | 188 | if err != nil { |
178 | return &TokenClaims{}, err | 189 | return &TokenClaims{}, err |
179 | } | 190 | } |
180 | 191 | ||
181 | // type assertion | 192 | // type assertion |
182 | claims, ok := parsedToken.Claims.(*TokenClaims) | 193 | claims, ok := parsedToken.Claims.(*TokenClaims) |
183 | if !ok || !parsedToken.Valid { | 194 | if !ok || !parsedToken.Valid { |
184 | return &TokenClaims{}, errors.New("token is not valid") | 195 | return &TokenClaims{}, errors.New("token is not valid") |
185 | } | 196 | } |
186 | return claims, err | 197 | return claims, err |
187 | } | 198 | } |
188 | 199 | ||
189 | // secretFunc returns byte slice of API secret keyword. | 200 | // secretFunc returns byte slice of API secret keyword. |
190 | func secretFunc(token *jwt.Token) (interface{}, error) { | 201 | func secretFunc(token *jwt.Token) (interface{}, error) { |
191 | return []byte(secret), nil | 202 | return []byte(secret), nil |
192 | } | 203 | } |
193 | 204 | ||
194 | // roleAuthorized returns true if role from userClaims matches any of the authorizedRoles | 205 | // rbacEnforce returns true if role that made HTTP request is authorized to |
195 | // or if authorizedRoles contains "*". | 206 | // access the resource it is targeting. |
196 | func roleAuthorized(authorizedRoles []string, userClaims *TokenClaims) bool { | 207 | // It exctracts user's role from the JWT token located in Authorization header of |
197 | if userClaims == nil { | 208 | // http.Request and then compares it with the list of supplied roles and returns |
209 | // true if there's a match, if "*" is provided or if the authRoles is nil. | ||
210 | // Otherwise it returns false. | ||
211 | func RbacCheck(req *http.Request, authRoles []string) bool { | ||
212 | if authRoles == nil { | ||
213 | return true | ||
214 | } | ||
215 | |||
216 | token := req.Header.Get("Authorization") | ||
217 | claims, err := ParseAPIToken(token) | ||
218 | if err != nil { | ||
198 | return false | 219 | return false |
199 | } | 220 | } |
200 | for _, r := range authorizedRoles { | 221 | |
201 | if userClaims.Role == r || r == "*" { | 222 | for _, r := range authRoles { |
223 | if claims.Role == r || r == "*" { | ||
202 | return true | 224 | return true |
203 | } | 225 | } |
204 | } | 226 | } |
227 | |||
205 | return false | 228 | return false |
206 | } | 229 | } |
230 | |||
231 | // Rbac sets common headers and performs RBAC. | ||
232 | // If RBAC passes it calls the handlerFunc. | ||
233 | func Rbac(handlerFunc http.HandlerFunc, authRoles []string) http.HandlerFunc { | ||
234 | return func(w http.ResponseWriter, req *http.Request) { | ||
235 | w.Header().Set("Access-Control-Allow-Origin", "*") | ||
236 | |||
237 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") | ||
238 | |||
239 | w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, | ||
240 | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) | ||
241 | |||
242 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | ||
243 | |||
244 | // TODO: Check for content type | ||
245 | |||
246 | if req.Method == "OPTIONS" { | ||
247 | return | ||
248 | } | ||
249 | |||
250 | err := req.ParseForm() | ||
251 | if err != nil { | ||
252 | BadRequestResponse(w, req) | ||
253 | return | ||
254 | } | ||
255 | |||
256 | if !RbacCheck(req, authRoles) { | ||
257 | UnauthorizedResponse(w, req) | ||
258 | return | ||
259 | } | ||
260 | |||
261 | // execute HandlerFunc | ||
262 | handlerFunc(w, req) |
http_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "net/http" | ||
5 | "encoding/json" | 4 | "encoding/json" |
5 | "net/http" | ||
6 | ) | 6 | ) |
7 | 7 | ||
8 | const templateHttpErr500_EN = "An internal server error has occurred." | 8 | const templateHttpErr500_EN = "An internal server error has occurred." |
9 | const templateHttpErr500_RS = "Došlo je do greške na serveru." | 9 | const templateHttpErr500_RS = "Došlo je do greške na serveru." |
10 | const templateHttpErr400_EN = "Bad request: invalid request body." | 10 | const templateHttpErr400_EN = "Bad request: invalid request body." |
11 | const templateHttpErr400_RS = "Neispravan zahtev." | 11 | const templateHttpErr400_RS = "Neispravan zahtev." |
12 | const templateHttpErr401_EN = "Unauthorized request." | 12 | const templateHttpErr401_EN = "Unauthorized request." |
13 | const templateHttpErr401_RS = "Neautorizovan zahtev." | 13 | const templateHttpErr401_RS = "Neautorizovan zahtev." |
14 | 14 | ||
15 | type httpError struct { | 15 | type httpError struct { |
16 | Error []HttpErrorDesc `json:"error"` | 16 | Error []HttpErrorDesc `json:"error"` |
17 | Request string `json:"request"` | 17 | Request string `json:"request"` |
18 | } | 18 | } |
19 | 19 | ||
20 | type HttpErrorDesc struct { | 20 | type HttpErrorDesc struct { |
21 | Lang string `json:"lang"` | 21 | Lang string `json:"lang"` |
22 | Desc string `json:"description"` | 22 | Desc string `json:"description"` |
23 | } | 23 | } |
24 | 24 | ||
25 | // ErrorResponse writes HTTP error to w. | 25 | // ErrorResponse writes HTTP error to w. |
26 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { | 26 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { |
27 | err := httpError{ desc, r.Method + " " + r.URL.Path } | 27 | err := httpError{desc, r.Method + " " + r.URL.Path} |
28 | w.WriteHeader(code) | 28 | w.WriteHeader(code) |
29 | json.NewEncoder(w).Encode(err) | 29 | json.NewEncoder(w).Encode(err) |
30 | } | 30 | } |
31 | 31 | ||
32 | // BadRequestResponse writes HTTP error 400 to w. | 32 | // BadRequestResponse writes HTTP error 400 to w. |
33 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { | 33 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { |
34 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ | 34 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ |
35 | { "en", templateHttpErr400_EN }, | 35 | {"en", templateHttpErr400_EN}, |
36 | { "rs", templateHttpErr400_RS }, | 36 | {"rs", templateHttpErr400_RS}, |
37 | }) | 37 | }) |
38 | } | 38 | } |
39 | 39 | ||
40 | // InternalSeverErrorResponse writes HTTP error 500 to w. | 40 | // InternalSeverErrorResponse writes HTTP error 500 to w. |
41 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { | 41 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { |
42 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ | 42 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ |
43 | { "en", templateHttpErr500_EN }, | 43 | {"en", templateHttpErr500_EN}, |
44 | { "rs", templateHttpErr500_RS }, | 44 | {"rs", templateHttpErr500_RS}, |
45 | }) | 45 | }) |
46 | } | 46 | } |
47 | 47 | ||
48 | // UnauthorizedError writes HTTP error 401 to w. | 48 | // UnauthorizedError writes HTTP error 401 to w. |
49 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { | 49 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { |
50 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ | 50 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ |
51 | { "en", templateHttpErr401_EN }, | 51 | {"en", templateHttpErr401_EN}, |
52 | { "rs", templateHttpErr401_RS }, | 52 | {"rs", templateHttpErr401_RS}, |
53 | }) | 53 | }) |
54 | } | 54 | } |
55 | 55 | ||
56 | // TODO: Check for content type | ||
57 | // WrapHandler sets common headers, checks for token validity and performs access control checks. | ||
58 | // If authentication passes it calls the handlerFunc. | ||
59 | func WrapHandler(handlerFunc http.HandlerFunc, authorizedRoles []string) http.HandlerFunc { | ||
60 | return func(w http.ResponseWriter, req *http.Request) { | ||
61 | w.Header().Set("Access-Control-Allow-Origin", "*") | ||
62 | |||
63 | w.Header().Set("Access-Control-Allow-Methods", | ||
64 | "POST, GET, PUT, DELETE, OPTIONS") | ||
65 | |||
66 | w.Header().Set("Access-Control-Allow-Headers", | ||
67 | `Accept, Content-Type, Content-Length, | ||
68 | Accept-Encoding, X-CSRF-Token, Authorization`) | ||
69 | |||
70 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | ||
71 | |||
72 | if req.Method == "OPTIONS" { | ||
73 | return | ||
74 | } | ||
75 | |||
76 | if authorizedRoles != nil { | ||
77 | token := req.Header.Get("Authorization") | ||
78 | claims, err := ParseAPIToken(token); | ||
79 | if err != nil || !roleAuthorized(authorizedRoles, claims) { | ||
80 | UnauthorizedResponse(w, req) | ||
81 | return | ||
82 | } | ||
83 | } | ||
84 | |||
85 | err := req.ParseForm() | ||
86 | if err != nil { | ||
87 | BadRequestResponse(w, req) | ||
88 | return | ||
89 | } | ||
90 | |||
91 | // execute HandlerFunc | ||
92 | handlerFunc(w, req) | ||
93 | } | ||
94 | } | ||
95 | |||
96 | // NotFoundHandler writes HTTP error 404 to w. | 56 | // NotFoundHandler writes HTTP error 404 to w. |
97 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { | 57 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { |
58 | SetDefaultHeaders(w) | ||
59 | if req.Method == "OPTIONS" { | ||
60 | return | ||
61 | } | ||
98 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ | 62 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ |
json_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | //"fmt" | 4 | //"fmt" |
5 | "net/http" | ||
6 | "encoding/json" | 5 | "encoding/json" |
7 | "errors" | 6 | "errors" |
8 | "io" | 7 | "io" |
8 | "net/http" | ||
9 | //"io/ioutil" | 9 | //"io/ioutil" |
10 | "sync" | 10 | "sync" |
11 | 11 | ||
12 | //"gopkg.in/rana/ora.v3" | 12 | //"gopkg.in/rana/ora.v3" |
13 | "gopkg.in/rana/ora.v4" | 13 | "gopkg.in/rana/ora.v4" |
14 | ) | 14 | ) |
15 | 15 | ||
16 | var mu = &sync.Mutex{} | 16 | var mu = &sync.Mutex{} |
17 | var payloads []payloadBuff | 17 | var payloads []payloadBuff |
18 | 18 | ||
19 | type LangMap map[string]map[string]string | 19 | type LangMap map[string]map[string]string |
20 | 20 | ||
21 | type Field struct { | 21 | type Field struct { |
22 | Parameter string `json:"param"` | 22 | Parameter string `json:"param"` |
23 | Type string `json:"type"` | 23 | Type string `json:"type"` |
24 | Visible bool `json:"visible"` | 24 | Visible bool `json:"visible"` |
25 | Editable bool `json:"editable"` | 25 | Editable bool `json:"editable"` |
26 | } | 26 | } |
27 | 27 | ||
28 | type CorrelationField struct { | 28 | type CorrelationField struct { |
29 | Result string `json:"result"` | 29 | Result string `json:"result"` |
30 | Elements []string `json:"elements"` | 30 | Elements []string `json:"elements"` |
31 | Type string `json:"type"` | 31 | Type string `json:"type"` |
32 | } | 32 | } |
33 | 33 | ||
34 | type Translation struct { | 34 | type Translation struct { |
35 | Language string `json:"language"` | 35 | Language string `json:"language"` |
36 | FieldsLabels map[string]string `json:"fieldsLabels"` | 36 | FieldsLabels map[string]string `json:"fieldsLabels"` |
37 | } | 37 | } |
38 | 38 | ||
39 | type payloadBuff struct { | 39 | type payloadBuff struct { |
40 | Type string `json:"tableType"` | 40 | Type string `json:"tableType"` |
41 | Method string `json:"method"` | 41 | Method string `json:"method"` |
42 | Params map[string]string `json:"params"` | 42 | Params map[string]string `json:"params"` |
43 | Lang []Translation `json:"lang"` | 43 | Lang []Translation `json:"lang"` |
44 | Fields []Field `json:"fields"` | 44 | Fields []Field `json:"fields"` |
45 | Correlations []CorrelationField `json:"correlationFields"` | 45 | Correlations []CorrelationField `json:"correlationFields"` |
46 | IdField string `json:"idField"` | 46 | IdField string `json:"idField"` |
47 | 47 | ||
48 | // Data can only hold slices of any type. It can't be used for itteration | 48 | // Data can only hold slices of any type. It can't be used for itteration |
49 | Data interface{} `json:"data"` | 49 | Data interface{} `json:"data"` |
50 | } | 50 | } |
51 | 51 | ||
52 | type Payload struct { | 52 | type Payload struct { |
53 | Method string `json:"method"` | 53 | Method string `json:"method"` |
54 | Params map[string]string `json:"params"` | 54 | Params map[string]string `json:"params"` |
55 | Lang []Translation `json:"lang"` | 55 | Lang []Translation `json:"lang"` |
56 | Fields []Field `json:"fields"` | 56 | Fields []Field `json:"fields"` |
57 | Correlations []CorrelationField `json:"correlationFields"` | 57 | Correlations []CorrelationField `json:"correlationFields"` |
58 | IdField string `json:"idField"` | 58 | IdField string `json:"idField"` |
59 | 59 | ||
60 | // Data can only hold slices of any type. It can't be used for itteration | 60 | // Data can only hold slices of any type. It can't be used for itteration |
61 | Data interface{} `json:"data"` | 61 | Data interface{} `json:"data"` |
62 | } | 62 | } |
63 | 63 | ||
64 | // NewPayload returs a payload sceleton for provided table. | 64 | // NewPayload returs a payload sceleton for provided table. |
65 | func NewPayload(r *http.Request, table string) Payload { | 65 | func NewPayload(r *http.Request, table string) Payload { |
66 | var pload Payload | 66 | var pload Payload |
67 | 67 | ||
68 | pload.Method = r.Method + " " + r.URL.Path | 68 | pload.Method = r.Method + " " + r.URL.Path |
69 | if table != "" { | 69 | if table != "" { |
70 | pload.Params = make(map[string]string, 0) | 70 | pload.Params = make(map[string]string, 0) |
71 | pload.Lang = translations(table) | 71 | pload.Lang = translations(table) |
72 | pload.Fields = fields(table) | 72 | pload.Fields = fields(table) |
73 | pload.IdField = id(table) | 73 | pload.IdField = id(table) |
74 | pload.Correlations = correlations(table) | 74 | pload.Correlations = correlations(table) |
75 | } | 75 | } |
76 | return pload | 76 | return pload |
77 | } | 77 | } |
78 | 78 | ||
79 | // DeliverPayload encodes payload to w. | 79 | // DeliverPayload encodes payload to w. |
80 | func DeliverPayload(w http.ResponseWriter, payload Payload) { | 80 | func DeliverPayload(w http.ResponseWriter, payload Payload) { |
81 | json.NewEncoder(w).Encode(payload) | 81 | json.NewEncoder(w).Encode(payload) |
82 | payload.Data = nil | 82 | payload.Data = nil |
83 | } | 83 | } |
84 | 84 | ||
85 | // translations returns a slice of translations for a payload/table of ptype type. | 85 | // translations returns a slice of translations for a payload/table of ptype type. |
86 | func translations(ptype string) []Translation { | 86 | func translations(ptype string) []Translation { |
87 | var translations []Translation | 87 | var translations []Translation |
88 | 88 | ||
89 | for _, pload := range payloads { | 89 | for _, pload := range payloads { |
90 | if pload.Type == ptype { | 90 | if pload.Type == ptype { |
91 | for _, t := range pload.Lang { | 91 | for _, t := range pload.Lang { |
92 | translations = append(translations, Translation{ | 92 | translations = append(translations, Translation{ |
93 | Language: t.Language, | 93 | Language: t.Language, |
94 | FieldsLabels: t.FieldsLabels, | 94 | FieldsLabels: t.FieldsLabels, |
95 | }) | 95 | }) |
96 | } | 96 | } |
97 | } | 97 | } |
98 | } | 98 | } |
99 | 99 | ||
100 | return translations | 100 | return translations |
101 | } | 101 | } |
102 | 102 | ||
103 | // fields returns a slice of fields for a payload/table of ptype type. | 103 | // fields returns a slice of fields for a payload/table of ptype type. |
104 | func fields(ptype string) []Field { | 104 | func fields(ptype string) []Field { |
105 | var fields []Field | 105 | var fields []Field |
106 | 106 | ||
107 | for _, pload := range payloads { | 107 | for _, pload := range payloads { |
108 | if pload.Type == ptype { | 108 | if pload.Type == ptype { |
109 | for _, f := range pload.Fields { | 109 | for _, f := range pload.Fields { |
110 | fields = append(fields, f) | 110 | fields = append(fields, f) |
111 | } | 111 | } |
112 | } | 112 | } |
113 | } | 113 | } |
114 | 114 | ||
115 | return fields | 115 | return fields |
116 | } | 116 | } |
117 | 117 | ||
118 | // id returns the name of ID field of a payload/table of ptype type. | 118 | // id returns the name of ID field of a payload/table of ptype type. |
119 | func id(ptype string) string { | 119 | func id(ptype string) string { |
120 | for _, pload := range payloads { | 120 | for _, pload := range payloads { |
121 | if pload.Type == ptype { | 121 | if pload.Type == ptype { |
122 | return pload.IdField | 122 | return pload.IdField |
123 | } | 123 | } |
124 | } | 124 | } |
125 | return "" | 125 | return "" |
126 | } | 126 | } |
127 | 127 | ||
128 | // correlations returns a slice of correlation fields for a payload/table of ptype type. | 128 | // correlations returns a slice of correlation fields for a payload/table of ptype type. |
129 | func correlations(ptype string) []CorrelationField { | 129 | func correlations(ptype string) []CorrelationField { |
130 | var corr []CorrelationField | 130 | var corr []CorrelationField |
131 | 131 | ||
132 | for _, pload := range payloads { | 132 | for _, pload := range payloads { |
133 | if pload.Type == ptype { | 133 | if pload.Type == ptype { |
134 | for _, c := range pload.Correlations { | 134 | for _, c := range pload.Correlations { |
135 | corr = append(corr, c) | 135 | corr = append(corr, c) |
136 | } | 136 | } |
137 | } | 137 | } |
138 | } | 138 | } |
139 | 139 | ||
140 | return corr | 140 | return corr |
141 | } | 141 | } |
142 | 142 | ||
143 | // InitTables loads all payloads in the payloads variable. | 143 | // InitTables loads all payloads in the payloads variable. |
144 | // Returns an error if it fails. | 144 | // Returns an error if it fails. |
145 | func InitTables(db *ora.Ses, project string) error { | 145 | func InitTables(db *ora.Ses, project string) error { |
146 | jsonbuf, err := fetchJSON(db, project) | 146 | jsonbuf, err := fetchJSON(db, project) |
147 | if err != nil { | 147 | if err != nil { |
148 | return err | 148 | return err |
149 | } | 149 | } |
150 | 150 | ||
151 | mu.Lock() | 151 | mu.Lock() |
152 | defer mu.Unlock() | 152 | defer mu.Unlock() |
153 | json.Unmarshal(jsonbuf, &payloads) | 153 | json.Unmarshal(jsonbuf, &payloads) |
154 | if len(payloads) == 0 { | 154 | if len(payloads) == 0 { |
155 | return errors.New("tables config is corrupt") | 155 | return errors.New("tables config is corrupt") |
156 | } | 156 | } |
157 | return nil | 157 | return nil |
158 | } | 158 | } |
159 | 159 | ||
160 | // fetchJSON returns a byte slice of JSON configuration file from TABLES_CONFIG table. | 160 | // fetchJSON returns a byte slice of JSON configuration file from TABLES_CONFIG table. |
161 | // Returns an error if it fails. | 161 | // Returns an error if it fails. |
162 | func fetchJSON(db *ora.Ses, project string) ([]byte, error) { | 162 | func fetchJSON(db *ora.Ses, project string) ([]byte, error) { |
163 | db.SetCfg(db.Cfg().SetClob(ora.S)) | 163 | db.SetCfg(db.Cfg().SetClob(ora.S)) |
164 | stmt, err := db.Prep(`SELECT JSON_NCLOB FROM TABLES_CONFIG WHERE PROJEKAT` + EqualQuotes(project), ora.S) | 164 | stmt, err := db.Prep(`SELECT JSON_NCLOB FROM TABLES_CONFIG WHERE PROJEKAT`+EqualQuotes(project), ora.S) |
165 | defer stmt.Close() | 165 | defer stmt.Close() |
166 | if err != nil { | 166 | if err != nil { |
167 | return nil, err | 167 | return nil, err |
168 | } | 168 | } |
169 | 169 | ||
170 | rset, err := stmt.Qry() | 170 | rset, err := stmt.Qry() |
171 | if err != nil { | 171 | if err != nil { |
172 | return nil, err | 172 | return nil, err |
173 | } | 173 | } |
174 | 174 | ||
175 | var data string | 175 | var data string |
176 | if rset.Next() { | 176 | if rset.Next() { |
177 | data = rset.Row[0].(string) | 177 | data = rset.Row[0].(string) |
178 | } | 178 | } |
179 | 179 | ||
180 | //fmt.Println(data) | 180 | //fmt.Println(data) |
181 | return []byte(data), nil | 181 | return []byte(data), nil |
182 | } | 182 | } |
183 | 183 | ||
184 | // DecodeJSON decodes JSON data from r to v. | 184 | // DecodeJSON decodes JSON data from r to v. |
185 | // Returns an error if it fails. | 185 | // Returns an error if it fails. |
186 | func DecodeJSON(r io.Reader, v interface{}) error { | 186 | func DecodeJSON(r io.Reader, v interface{}) error { |
187 | return json.NewDecoder(r).Decode(v) | 187 | return json.NewDecoder(r).Decode(v) |
188 | } | 188 | } |
select_config.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | //import "gopkg.in/rana/ora.v3" | 3 | //import "gopkg.in/rana/ora.v3" |
4 | import "gopkg.in/rana/ora.v4" | 4 | import "gopkg.in/rana/ora.v4" |
5 | 5 | ||
6 | type SelectConfig struct { | 6 | type SelectConfig struct { |
7 | ListObjType string `json:"listObjectType"` | 7 | ListObjType string `json:"listObjectType"` |
8 | ObjType string `json:"objectType"` | 8 | ObjType string `json:"objectType"` |
9 | Type string `json:"type"` | 9 | Type string `json:"type"` |
10 | IdField string `json:"idField"` | 10 | IdField string `json:"idField"` |
11 | LabelField string `json:"labelField"` | 11 | LabelField string `json:"labelField"` |
12 | ValueField string `json:"valueField"` | 12 | ValueField string `json:"valueField"` |
13 | } | 13 | } |
14 | 14 | ||
15 | // GetSelectConfig returns select configuration slice for the given object type. | 15 | // GetSelectConfig returns select configuration slice for the given object type. |
16 | func GetSelectConfig(db *ora.Ses, otype string) ([]SelectConfig, error) { | 16 | func GetSelectConfig(db *ora.Ses, otype string) ([]SelectConfig, error) { |
17 | resp := make([]SelectConfig, 0) | 17 | resp := make([]SelectConfig, 0) |
18 | var err error | 18 | var err error |
19 | var stmt *ora.Stmt | 19 | var stmt *ora.Stmt |
20 | query := `SELECT a.LIST_OBJECT_TYPE, a.OBJECT_TYPE, a.ID_FIELD, | 20 | query := `SELECT |
21 | a.LABEL_FIELD, a.TYPE, b.FIELD | 21 | a.LIST_OBJECT_TYPE, |
22 | a.OBJECT_TYPE, | ||
23 | a.ID_FIELD, | ||
24 | a.LABEL_FIELD, | ||
25 | a.TYPE, | ||
26 | b.FIELD | ||
22 | FROM LIST_SELECT_CONFIG a, LIST_VALUE_FIELD b | 27 | FROM LIST_SELECT_CONFIG a, LIST_VALUE_FIELD b |
23 | WHERE a.LIST_OBJECT_TYPE` + otype + ` | 28 | WHERE a.LIST_OBJECT_TYPE` + otype + ` |
24 | AND b.LIST_TYPE = a.LIST_OBJECT_TYPE | 29 | AND b.LIST_TYPE = a.LIST_OBJECT_TYPE |
25 | AND b.OBJECT_TYPE = a.OBJECT_TYPE` | 30 | AND b.OBJECT_TYPE = a.OBJECT_TYPE` |
26 | 31 | ||
27 | stmt, err = db.Prep(query, ora.S, ora.S, ora.S, ora.S, ora.S, ora.S) | 32 | stmt, err = db.Prep(query, ora.S, ora.S, ora.S, ora.S, ora.S, ora.S) |
28 | defer stmt.Close() | 33 | defer stmt.Close() |
29 | if err != nil { | 34 | if err != nil { |
30 | return nil, err | 35 | return nil, err |
31 | } | 36 | } |
32 | 37 | ||
33 | rset, err := stmt.Qry() | 38 | rset, err := stmt.Qry() |
34 | if err != nil { | 39 | if err != nil { |
35 | return nil, err | 40 | return nil, err |
36 | } | 41 | } |
37 | for rset.Next() { | 42 | for rset.Next() { |
38 | resp = append(resp, SelectConfig{ | 43 | resp = append(resp, SelectConfig{ |
39 | ListObjType: rset.Row[0].(string), | 44 | ListObjType: rset.Row[0].(string), |
40 | ObjType: rset.Row[1].(string), | 45 | ObjType: rset.Row[1].(string), |
41 | IdField: rset.Row[2].(string), | 46 | IdField: rset.Row[2].(string), |
42 | LabelField: rset.Row[3].(string), | 47 | LabelField: rset.Row[3].(string), |
43 | Type: rset.Row[4].(string), | 48 | Type: rset.Row[4].(string), |
44 | ValueField: rset.Row[5].(string), | 49 | ValueField: rset.Row[5].(string), |
45 | }) | 50 | }) |
46 | } | 51 | } |
47 | if rset.Err() != nil { | 52 | if rset.Err() != nil { |
48 | return nil, rset.Err() | 53 | return nil, rset.Err() |
49 | } | 54 | } |
50 | 55 | ||
51 | return resp, nil | 56 | return resp, nil |
52 | } | 57 | } |
53 | 58 |