Commit 6620591d867ec0a4fc4a3dcc5ed206061d829b1c
1 parent
62a69bedaa
Exists in
master
and in
1 other branch
moved DeliverPaylaod to http_utility
Showing
3 changed files
with
27 additions
and
14 deletions
Show diff stats
auth_utility.go
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 | const appName = "korisnicki-centar" |
19 | const secret = "korisnicki-centar-api" | 19 | const secret = "korisnicki-centar-api" |
20 | 20 | ||
21 | type Role struct { | 21 | type Role struct { |
22 | Name string `json:"name"` | 22 | Name string `json:"name"` |
23 | ID uint32 `json:"id"` | 23 | ID uint32 `json:"id"` |
24 | } | 24 | } |
25 | 25 | ||
26 | // TokenClaims are JWT token claims. | 26 | // TokenClaims are JWT token claims. |
27 | type TokenClaims struct { | 27 | type TokenClaims struct { |
28 | Token string `json:"access_token"` | 28 | Token string `json:"access_token"` |
29 | TokenType string `json:"token_type"` | 29 | TokenType string `json:"token_type"` |
30 | Username string `json:"username"` | 30 | Username string `json:"username"` |
31 | Role string `json:"role"` | 31 | Role string `json:"role"` |
32 | RoleID uint32 `json:"role_id"` | 32 | RoleID uint32 `json:"role_id"` |
33 | ExpiresIn int64 `json:"expires_in"` | 33 | ExpiresIn int64 `json:"expires_in"` |
34 | jwt.StandardClaims // extending a struct | 34 | |
35 | // extending a struct | ||
36 | jwt.StandardClaims | ||
35 | } | 37 | } |
36 | 38 | ||
37 | // CredentialsStruct is an instace of username/password values. | 39 | // CredentialsStruct is an instace of username/password values. |
38 | type CredentialsStruct struct { | 40 | type CredentialsStruct struct { |
39 | Username string `json:"username"` | 41 | Username string `json:"username"` |
40 | Password string `json:"password"` | 42 | Password string `json:"password"` |
41 | } | 43 | } |
42 | 44 | ||
43 | // ValidateCredentials hashes pass and salt and returns comparison result with resultHash | 45 | // ValidateCredentials hashes pass and salt and returns comparison result with resultHash |
44 | func ValidateCredentials(pass, salt, resultHash string) bool { | 46 | func ValidateCredentials(pass, salt, resultHash string) bool { |
45 | hash, _, err := CreateHash(pass, salt) | 47 | hash, _, err := CreateHash(pass, salt) |
46 | if err != nil { | 48 | if err != nil { |
47 | return false | 49 | return false |
48 | } | 50 | } |
49 | return hash == resultHash | 51 | return hash == resultHash |
50 | } | 52 | } |
51 | 53 | ||
52 | // CreateHash hashes str using SHA256. | 54 | // CreateHash hashes str using SHA256. |
53 | // If the presalt parameter is not provided CreateHash will generate new salt string. | 55 | // If the presalt parameter is not provided CreateHash will generate new salt string. |
54 | // Returns hash and salt strings or an error if it fails. | 56 | // Returns hash and salt strings or an error if it fails. |
55 | func CreateHash(str, presalt string) (hash, salt string, err error) { | 57 | func CreateHash(str, presalt string) (hash, salt string, err error) { |
56 | // chech if message is presalted | 58 | // chech if message is presalted |
57 | if presalt == "" { | 59 | if presalt == "" { |
58 | salt, err = randomSalt() | 60 | salt, err = randomSalt() |
59 | if err != nil { | 61 | if err != nil { |
60 | return "", "", err | 62 | return "", "", err |
61 | } | 63 | } |
62 | } else { | 64 | } else { |
63 | salt = presalt | 65 | salt = presalt |
64 | } | 66 | } |
65 | 67 | ||
66 | // convert strings to raw byte slices | 68 | // convert strings to raw byte slices |
67 | rawstr := []byte(str) | 69 | rawstr := []byte(str) |
68 | rawsalt, err := hex.DecodeString(salt) | 70 | rawsalt, err := hex.DecodeString(salt) |
69 | if err != nil { | 71 | if err != nil { |
70 | return "", "", err | 72 | return "", "", err |
71 | } | 73 | } |
72 | 74 | ||
73 | rawdata := make([]byte, len(rawstr)+len(rawsalt)) | 75 | rawdata := make([]byte, len(rawstr)+len(rawsalt)) |
74 | rawdata = append(rawdata, rawstr...) | 76 | rawdata = append(rawdata, rawstr...) |
75 | rawdata = append(rawdata, rawsalt...) | 77 | rawdata = append(rawdata, rawsalt...) |
76 | 78 | ||
77 | // hash message + salt | 79 | // hash message + salt |
78 | hasher := sha256.New() | 80 | hasher := sha256.New() |
79 | hasher.Write(rawdata) | 81 | hasher.Write(rawdata) |
80 | rawhash := hasher.Sum(nil) | 82 | rawhash := hasher.Sum(nil) |
81 | 83 | ||
82 | hash = hex.EncodeToString(rawhash) | 84 | hash = hex.EncodeToString(rawhash) |
83 | return hash, salt, nil | 85 | return hash, salt, nil |
84 | } | 86 | } |
85 | 87 | ||
86 | // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims. | 88 | // CreateAuthToken returns JWT token with encoded username, role, expiration date and issuer claims. |
87 | // It returns an error if it fails. | 89 | // It returns an error if it fails. |
88 | func CreateAuthToken(username string, role Role) (TokenClaims, error) { | 90 | func CreateAuthToken(username string, role Role) (TokenClaims, error) { |
89 | t0 := (time.Now()).Unix() | 91 | t0 := (time.Now()).Unix() |
90 | t1 := (time.Now().Add(OneWeek)).Unix() | 92 | t1 := (time.Now().Add(OneWeek)).Unix() |
91 | claims := TokenClaims{ | 93 | claims := TokenClaims{ |
92 | TokenType: "Bearer", | 94 | TokenType: "Bearer", |
93 | Username: username, | 95 | Username: username, |
94 | Role: role.Name, | 96 | Role: role.Name, |
95 | RoleID: role.ID, | 97 | RoleID: role.ID, |
96 | ExpiresIn: t1 - t0, | 98 | ExpiresIn: t1 - t0, |
97 | } | 99 | } |
98 | // initialize jwt.StandardClaims fields (anonymous struct) | 100 | // initialize jwt.StandardClaims fields (anonymous struct) |
99 | claims.IssuedAt = t0 | 101 | claims.IssuedAt = t0 |
100 | claims.ExpiresAt = t1 | 102 | claims.ExpiresAt = t1 |
101 | claims.Issuer = appName | 103 | claims.Issuer = appName |
102 | 104 | ||
103 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 105 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
104 | token, err := jwtToken.SignedString([]byte(secret)) | 106 | token, err := jwtToken.SignedString([]byte(secret)) |
105 | if err != nil { | 107 | if err != nil { |
106 | return TokenClaims{}, err | 108 | return TokenClaims{}, err |
107 | } | 109 | } |
108 | claims.Token = token | 110 | claims.Token = token |
109 | return claims, nil | 111 | return claims, nil |
110 | } | 112 | } |
111 | 113 | ||
112 | // RefreshAuthToken prolongs JWT token's expiration date for one week. | 114 | // RefreshAuthToken prolongs JWT token's expiration date for one week. |
113 | // It returns new JWT token or an error if it fails. | 115 | // It returns new JWT token or an error if it fails. |
114 | func RefreshAuthToken(req *http.Request) (TokenClaims, error) { | 116 | func RefreshAuthToken(req *http.Request) (TokenClaims, error) { |
115 | authHead := req.Header.Get("Authorization") | 117 | authHead := req.Header.Get("Authorization") |
116 | tokenstr := strings.TrimPrefix(authHead, "Bearer ") | 118 | tokenstr := strings.TrimPrefix(authHead, "Bearer ") |
117 | token, err := jwt.ParseWithClaims(tokenstr, &TokenClaims{}, secretFunc) | 119 | token, err := jwt.ParseWithClaims(tokenstr, &TokenClaims{}, secretFunc) |
118 | if err != nil { | 120 | if err != nil { |
119 | return TokenClaims{}, err | 121 | return TokenClaims{}, err |
120 | } | 122 | } |
121 | 123 | ||
122 | // type assertion | 124 | // type assertion |
123 | claims, ok := token.Claims.(*TokenClaims) | 125 | claims, ok := token.Claims.(*TokenClaims) |
124 | if !ok || !token.Valid { | 126 | if !ok || !token.Valid { |
125 | return TokenClaims{}, errors.New("token is not valid") | 127 | return TokenClaims{}, errors.New("token is not valid") |
126 | } | 128 | } |
127 | 129 | ||
128 | // extend token expiration date | 130 | // extend token expiration date |
129 | return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID}) | 131 | return CreateAuthToken(claims.Username, Role{claims.Role, claims.RoleID}) |
130 | } | 132 | } |
131 | 133 | ||
132 | // RbacCheck returns true if role that made HTTP request is authorized to | 134 | // RbacCheck returns true if role that made HTTP request is authorized to |
133 | // access the resource it is targeting. | 135 | // access the resource it is targeting. |
134 | // It exctracts user's role from the JWT token located in Authorization header of | 136 | // It exctracts user's role from the JWT token located in Authorization header of |
135 | // http.Request and then compares it with the list of supplied roles and returns | 137 | // http.Request and then compares it with the list of supplied roles and returns |
136 | // true if there's a match, if "*" is provided or if the authRoles is nil. | 138 | // true if there's a match, if "*" is provided or if the authRoles is nil. |
137 | // Otherwise it returns false. | 139 | // Otherwise it returns false. |
138 | func RbacCheck(req *http.Request, authRoles []string) bool { | 140 | func RbacCheck(req *http.Request, authRoles []string) bool { |
139 | if authRoles == nil { | 141 | if authRoles == nil { |
140 | return true | 142 | return true |
141 | } | 143 | } |
142 | 144 | ||
143 | // validate token and check expiration date | 145 | // validate token and check expiration date |
144 | claims, err := GetTokenClaims(req) | 146 | claims, err := GetTokenClaims(req) |
145 | if err != nil { | 147 | if err != nil { |
146 | return false | 148 | return false |
147 | } | 149 | } |
148 | // check if token has expired | 150 | // check if token has expired |
149 | if claims.ExpiresAt < (time.Now()).Unix() { | 151 | if claims.ExpiresAt < (time.Now()).Unix() { |
150 | return false | 152 | return false |
151 | } | 153 | } |
152 | 154 | ||
153 | // check if role extracted from token matches | 155 | // check if role extracted from token matches |
154 | // any of the provided (allowed) ones | 156 | // any of the provided (allowed) ones |
155 | for _, r := range authRoles { | 157 | for _, r := range authRoles { |
156 | if claims.Role == r || r == "*" { | 158 | if claims.Role == r || r == "*" { |
157 | return true | 159 | return true |
158 | } | 160 | } |
159 | } | 161 | } |
160 | 162 | ||
161 | return false | 163 | return false |
162 | } | 164 | } |
163 | 165 | ||
164 | // TODO | 166 | // TODO |
165 | func AuthCheck(req *http.Request, authRoles []string) (*TokenClaims, error) { | 167 | func AuthCheck(req *http.Request, authRoles []string) (*TokenClaims, error) { |
166 | if authRoles == nil { | 168 | if authRoles == nil { |
167 | return &TokenClaims{}, nil | 169 | return &TokenClaims{}, nil |
168 | } | 170 | } |
169 | 171 | ||
170 | // validate token and check expiration date | 172 | // validate token and check expiration date |
171 | claims, err := GetTokenClaims(req) | 173 | claims, err := GetTokenClaims(req) |
172 | if err != nil { | 174 | if err != nil { |
173 | return &TokenClaims{}, err | 175 | return &TokenClaims{}, err |
174 | } | 176 | } |
175 | // check if token has expired | 177 | // check if token has expired |
176 | if claims.ExpiresAt < (time.Now()).Unix() { | 178 | if claims.ExpiresAt < (time.Now()).Unix() { |
177 | return &TokenClaims{}, errors.New("token has expired") | 179 | return &TokenClaims{}, errors.New("token has expired") |
178 | } | 180 | } |
179 | 181 | ||
180 | // check if role extracted from token matches | 182 | // check if role extracted from token matches |
181 | // any of the provided (allowed) ones | 183 | // any of the provided (allowed) ones |
182 | for _, r := range authRoles { | 184 | for _, r := range authRoles { |
183 | if claims.Role == r || r == "*" { | 185 | if claims.Role == r || r == "*" { |
184 | return claims, nil | 186 | return claims, nil |
185 | } | 187 | } |
186 | } | 188 | } |
187 | 189 | ||
188 | return claims, errors.New("role is not authorized") | 190 | return claims, errors.New("role is not authorized") |
189 | } | 191 | } |
190 | 192 | ||
191 | // GetTokenClaims extracts JWT claims from Authorization header of the request. | 193 | // GetTokenClaims extracts JWT claims from Authorization header of the request. |
192 | // Returns token claims or an error. | 194 | // Returns token claims or an error. |
193 | func GetTokenClaims(req *http.Request) (*TokenClaims, error) { | 195 | func GetTokenClaims(req *http.Request) (*TokenClaims, error) { |
194 | // check for and strip 'Bearer' prefix | 196 | // check for and strip 'Bearer' prefix |
195 | var tokstr string | 197 | var tokstr string |
196 | authHead := req.Header.Get("Authorization") | 198 | authHead := req.Header.Get("Authorization") |
197 | if ok := strings.HasPrefix(authHead, "Bearer "); ok { | 199 | if ok := strings.HasPrefix(authHead, "Bearer "); ok { |
198 | tokstr = strings.TrimPrefix(authHead, "Bearer ") | 200 | tokstr = strings.TrimPrefix(authHead, "Bearer ") |
199 | } else { | 201 | } else { |
200 | return &TokenClaims{}, errors.New("authorization header in incomplete") | 202 | return &TokenClaims{}, errors.New("authorization header in incomplete") |
201 | } | 203 | } |
202 | 204 | ||
203 | token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc) | 205 | token, err := jwt.ParseWithClaims(tokstr, &TokenClaims{}, secretFunc) |
204 | if err != nil { | 206 | if err != nil { |
205 | return &TokenClaims{}, err | 207 | return &TokenClaims{}, err |
206 | } | 208 | } |
207 | 209 | ||
208 | // type assertion | 210 | // type assertion |
209 | claims, ok := token.Claims.(*TokenClaims) | 211 | claims, ok := token.Claims.(*TokenClaims) |
210 | if !ok || !token.Valid { | 212 | if !ok || !token.Valid { |
211 | return &TokenClaims{}, errors.New("token is not valid") | 213 | return &TokenClaims{}, errors.New("token is not valid") |
212 | } | 214 | } |
213 | 215 | ||
214 | return claims, nil | 216 | return claims, nil |
215 | } | 217 | } |
216 | 218 | ||
217 | // randomSalt returns a string of random characters of 'saltSize' length. | 219 | // randomSalt returns a string of random characters of 'saltSize' length. |
218 | func randomSalt() (s string, err error) { | 220 | func randomSalt() (s string, err error) { |
219 | rawsalt := make([]byte, saltSize) | 221 | rawsalt := make([]byte, saltSize) |
220 | 222 | ||
221 | _, err = rand.Read(rawsalt) | 223 | _, err = rand.Read(rawsalt) |
222 | if err != nil { | 224 | if err != nil { |
223 | return "", err | 225 | return "", err |
224 | } | 226 | } |
225 | 227 | ||
226 | s = hex.EncodeToString(rawsalt) | 228 | s = hex.EncodeToString(rawsalt) |
227 | return s, nil | 229 | return s, nil |
228 | } | 230 | } |
229 | 231 | ||
230 | // secretFunc returns byte slice of API secret keyword. | 232 | // secretFunc returns byte slice of API secret keyword. |
231 | func secretFunc(token *jwt.Token) (interface{}, error) { | 233 | func secretFunc(token *jwt.Token) (interface{}, error) { |
232 | return []byte(secret), nil | 234 | return []byte(secret), nil |
233 | } | 235 | } |
234 | 236 |
http_utility.go
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 | const ( | 8 | const ( |
9 | templateHttpErr500_EN = "An internal server error has occurred." | 9 | templateHttpErr500_EN = "An internal server error has occurred." |
10 | templateHttpErr500_RS = "Došlo je do greške na serveru." | 10 | templateHttpErr500_RS = "Došlo je do greške na serveru." |
11 | templateHttpErr400_EN = "Bad request: invalid request body." | 11 | templateHttpErr400_EN = "Bad request: invalid request body." |
12 | templateHttpErr400_RS = "Neispravan zahtev." | 12 | templateHttpErr400_RS = "Neispravan zahtev." |
13 | templateHttpErr404_EN = "Resource not found." | ||
14 | templateHttpErr404_RS = "Objekat nije pronadjen." | ||
13 | templateHttpErr401_EN = "Unauthorized request." | 15 | templateHttpErr401_EN = "Unauthorized request." |
14 | templateHttpErr401_RS = "Neautorizovan zahtev." | 16 | templateHttpErr401_RS = "Neautorizovan zahtev." |
15 | ) | 17 | ) |
16 | 18 | ||
17 | type httpError struct { | 19 | type httpError struct { |
18 | Error []HttpErrorDesc `json:"error"` | 20 | Error []HttpErrorDesc `json:"error"` |
19 | Request string `json:"request"` | 21 | Request string `json:"request"` |
20 | } | 22 | } |
21 | 23 | ||
22 | type HttpErrorDesc struct { | 24 | type HttpErrorDesc struct { |
23 | Lang string `json:"lang"` | 25 | Lang string `json:"lang"` |
24 | Desc string `json:"description"` | 26 | Desc string `json:"description"` |
25 | } | 27 | } |
26 | 28 | ||
29 | // DeliverPayload encodes payload as JSON to w. | ||
30 | func DeliverPayload(w http.ResponseWriter, payload Payload) { | ||
31 | // Don't write status OK in the headers here. Leave it up for the caller. | ||
32 | // E.g. Status 201. | ||
33 | json.NewEncoder(w).Encode(payload) | ||
34 | payload.Data = nil | ||
35 | } | ||
36 | |||
27 | // ErrorResponse writes HTTP error to w. | 37 | // ErrorResponse writes HTTP error to w. |
28 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { | 38 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { |
29 | //err := httpError{desc, r.Method + " " + r.URL.Path} | ||
30 | err := httpError{desc, r.Method + " " + r.RequestURI} | 39 | err := httpError{desc, r.Method + " " + r.RequestURI} |
31 | w.WriteHeader(code) | 40 | w.WriteHeader(code) |
32 | json.NewEncoder(w).Encode(err) | 41 | json.NewEncoder(w).Encode(err) |
33 | } | 42 | } |
34 | 43 | ||
44 | // NotFoundResponse writes HTTP error 404 to w. | ||
45 | func NotFoundResponse(w http.ResponseWriter, req *http.Request) { | ||
46 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ | ||
47 | {"en", templateHttpErr404_EN}, | ||
48 | {"rs", templateHttpErr404_RS}, | ||
49 | }) | ||
50 | } | ||
51 | |||
35 | // BadRequestResponse writes HTTP error 400 to w. | 52 | // BadRequestResponse writes HTTP error 400 to w. |
36 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { | 53 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { |
37 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ | 54 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ |
38 | {"en", templateHttpErr400_EN}, | 55 | {"en", templateHttpErr400_EN}, |
39 | {"rs", templateHttpErr400_RS}, | 56 | {"rs", templateHttpErr400_RS}, |
40 | }) | 57 | }) |
41 | } | 58 | } |
42 | 59 | ||
43 | // InternalSeverErrorResponse writes HTTP error 500 to w. | 60 | // InternalSeverErrorResponse writes HTTP error 500 to w. |
44 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { | 61 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { |
45 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ | 62 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ |
46 | {"en", templateHttpErr500_EN}, | 63 | {"en", templateHttpErr500_EN}, |
47 | {"rs", templateHttpErr500_RS}, | 64 | {"rs", templateHttpErr500_RS}, |
48 | }) | 65 | }) |
49 | } | 66 | } |
50 | 67 | ||
51 | // UnauthorizedError writes HTTP error 401 to w. | 68 | // UnauthorizedError writes HTTP error 401 to w. |
52 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { | 69 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { |
53 | w.Header().Set("WWW-Authenticate", "Bearer") | 70 | w.Header().Set("WWW-Authenticate", "Bearer") |
54 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ | 71 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ |
55 | {"en", templateHttpErr401_EN}, | 72 | {"en", templateHttpErr401_EN}, |
56 | {"rs", templateHttpErr401_RS}, | 73 | {"rs", templateHttpErr401_RS}, |
57 | }) | 74 | }) |
58 | } | 75 | } |
59 | 76 | ||
60 | // NotFoundHandler writes HTTP error 404 to w. | 77 | // NotFoundHandler writes HTTP error 404 to w. |
61 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { | 78 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { |
62 | SetDefaultHeaders(w) | 79 | SetDefaultHeaders(w) |
63 | if req.Method == "OPTIONS" { | 80 | if req.Method == "OPTIONS" { |
64 | return | 81 | return |
65 | } | 82 | } |
66 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ | 83 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ |
67 | {"en", "Not found."}, | 84 | {"en", "Not found."}, |
68 | {"rs", "Traženi resurs ne postoji."}, | 85 | {"rs", "Traženi resurs ne postoji."}, |
69 | }) | 86 | }) |
70 | } | 87 | } |
71 | 88 | ||
72 | // SetDefaultHeaders set's default headers for an HTTP response. | 89 | // SetDefaultHeaders set's default headers for an HTTP response. |
73 | func SetDefaultHeaders(w http.ResponseWriter) { | 90 | func SetDefaultHeaders(w http.ResponseWriter) { |
74 | w.Header().Set("Access-Control-Allow-Origin", "*") | 91 | w.Header().Set("Access-Control-Allow-Origin", "*") |
75 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") | 92 | w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") |
76 | w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, | 93 | w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, |
77 | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) | 94 | Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) |
78 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | 95 | w.Header().Set("Content-Type", "application/json; charset=utf-8") |
79 | } | 96 | } |
json_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "encoding/json" | 4 | "encoding/json" |
5 | "errors" | 5 | "errors" |
6 | "fmt" | 6 | "fmt" |
7 | "io" | 7 | "io" |
8 | "net/http" | 8 | "net/http" |
9 | "sync" | 9 | "sync" |
10 | 10 | ||
11 | "gopkg.in/rana/ora.v4" | 11 | "gopkg.in/rana/ora.v4" |
12 | ) | 12 | ) |
13 | 13 | ||
14 | var mu = &sync.Mutex{} | 14 | var mu = &sync.Mutex{} |
15 | var payloads []Payload | 15 | var payloads []Payload |
16 | 16 | ||
17 | type LangMap map[string]map[string]string | 17 | type LangMap map[string]map[string]string |
18 | 18 | ||
19 | type Field struct { | 19 | type Field struct { |
20 | Parameter string `json:"param"` | 20 | Parameter string `json:"param"` |
21 | Type string `json:"type"` | 21 | Type string `json:"type"` |
22 | Visible bool `json:"visible"` | 22 | Visible bool `json:"visible"` |
23 | Editable bool `json:"editable"` | 23 | Editable bool `json:"editable"` |
24 | } | 24 | } |
25 | 25 | ||
26 | type CorrelationField struct { | 26 | type CorrelationField struct { |
27 | Result string `json:"result"` | 27 | Result string `json:"result"` |
28 | Elements []string `json:"elements"` | 28 | Elements []string `json:"elements"` |
29 | Type string `json:"type"` | 29 | Type string `json:"type"` |
30 | } | 30 | } |
31 | 31 | ||
32 | type Translation struct { | 32 | type Translation struct { |
33 | Language string `json:"language"` | 33 | Language string `json:"language"` |
34 | FieldsLabels map[string]string `json:"fieldsLabels"` | 34 | FieldsLabels map[string]string `json:"fieldsLabels"` |
35 | } | 35 | } |
36 | 36 | ||
37 | type Payload struct { | 37 | type Payload struct { |
38 | Type string `json:"type"` | 38 | Type string `json:"type"` |
39 | Method string `json:"method"` | 39 | Method string `json:"method"` |
40 | Params map[string]string `json:"params"` | 40 | Params map[string]string `json:"params"` |
41 | Lang []Translation `json:"lang"` | 41 | Lang []Translation `json:"lang"` |
42 | Fields []Field `json:"fields"` | 42 | Fields []Field `json:"fields"` |
43 | Correlations []CorrelationField `json:"correlationFields"` | 43 | Correlations []CorrelationField `json:"correlationFields"` |
44 | IdField string `json:"idField"` | 44 | IdField string `json:"idField"` |
45 | 45 | ||
46 | // Data holds JSON payload. It can't be used for itteration. | 46 | // Data holds JSON payload. It can't be used for itteration. |
47 | Data interface{} `json:"data"` | 47 | Data interface{} `json:"data"` |
48 | } | 48 | } |
49 | 49 | ||
50 | // InitPayloadsMetaData loads all payloads in the payloads variable. | 50 | // InitPayloadsMetaData loads all payloads in the payloads variable. |
51 | // Returns an error if it fails. | 51 | // Returns an error if it fails. |
52 | func InitPayloadsMetaData(db *ora.Ses, project string) error { | 52 | func InitPayloadsMetaData(db *ora.Ses, project string) error { |
53 | payloads = make([]Payload, 0) | 53 | payloads = make([]Payload, 0) |
54 | 54 | ||
55 | jsonbuf, err := fetchJSON(db, project) | 55 | jsonbuf, err := fetchJSON(db, project) |
56 | if err != nil { | 56 | if err != nil { |
57 | return err | 57 | return err |
58 | } | 58 | } |
59 | 59 | ||
60 | mu.Lock() | 60 | mu.Lock() |
61 | defer mu.Unlock() | 61 | defer mu.Unlock() |
62 | json.Unmarshal(jsonbuf, &payloads) | 62 | json.Unmarshal(jsonbuf, &payloads) |
63 | if len(payloads) == 0 { | 63 | if len(payloads) == 0 { |
64 | return errors.New("tables config is corrupt") | 64 | return errors.New("tables config is corrupt") |
65 | } | 65 | } |
66 | return nil | 66 | return nil |
67 | } | 67 | } |
68 | 68 | ||
69 | // DecodeJSON decodes JSON data from r to v. | 69 | // DecodeJSON decodes JSON data from r to v. |
70 | // Returns an error if it fails. | 70 | // Returns an error if it fails. |
71 | func DecodeJSON(r io.Reader, v interface{}) error { | 71 | func DecodeJSON(r io.Reader, v interface{}) error { |
72 | return json.NewDecoder(r).Decode(v) | 72 | return json.NewDecoder(r).Decode(v) |
73 | } | 73 | } |
74 | 74 | ||
75 | // NewPayload returs a payload sceleton for provided table. | 75 | // NewPayload returs a payload sceleton for provided table. |
76 | func NewPayload(r *http.Request, table string) Payload { | 76 | func NewPayload(r *http.Request, table string) Payload { |
77 | var pload Payload | 77 | var pload Payload |
78 | 78 | ||
79 | pload.Method = r.Method + " " + r.RequestURI | 79 | pload.Method = r.Method + " " + r.RequestURI |
80 | pload.Type = table | 80 | pload.Type = table |
81 | if table != "" { | 81 | if table != "" { |
82 | pload.Params = make(map[string]string, 0) | 82 | pload.Params = make(map[string]string, 0) |
83 | pload.Lang = translations(table) | 83 | pload.Lang = translations(table) |
84 | pload.Fields = fields(table) | 84 | pload.Fields = fields(table) |
85 | pload.IdField = id(table) | 85 | pload.IdField = id(table) |
86 | pload.Correlations = correlations(table) | 86 | pload.Correlations = correlations(table) |
87 | } | 87 | } |
88 | return pload | 88 | return pload |
89 | } | 89 | } |
90 | 90 | ||
91 | // DeliverPayload encodes payload to w. | ||
92 | func DeliverPayload(w http.ResponseWriter, payload Payload) { | ||
93 | json.NewEncoder(w).Encode(payload) | ||
94 | payload.Data = nil | ||
95 | } | ||
96 | |||
97 | // translations returns a slice of translations for a payload/table of ptype type. | 91 | // translations returns a slice of translations for a payload/table of ptype type. |
98 | func translations(ptype string) []Translation { | 92 | func translations(ptype string) []Translation { |
99 | var translations []Translation | 93 | var translations []Translation |
100 | 94 | ||
101 | for _, pload := range payloads { | 95 | for _, pload := range payloads { |
102 | if pload.Type == ptype { | 96 | if pload.Type == ptype { |
103 | for _, t := range pload.Lang { | 97 | for _, t := range pload.Lang { |
104 | translations = append(translations, Translation{ | 98 | translations = append(translations, Translation{ |
105 | Language: t.Language, | 99 | Language: t.Language, |
106 | FieldsLabels: t.FieldsLabels, | 100 | FieldsLabels: t.FieldsLabels, |
107 | }) | 101 | }) |
108 | } | 102 | } |
109 | } | 103 | } |
110 | } | 104 | } |
111 | 105 | ||
112 | return translations | 106 | return translations |
113 | } | 107 | } |
114 | 108 | ||
115 | // fields returns a slice of fields for a payload/table of ptype type. | 109 | // fields returns a slice of fields for a payload/table of ptype type. |
116 | func fields(ptype string) []Field { | 110 | func fields(ptype string) []Field { |
117 | var fields []Field | 111 | var fields []Field |
118 | 112 | ||
119 | for _, pload := range payloads { | 113 | for _, pload := range payloads { |
120 | if pload.Type == ptype { | 114 | if pload.Type == ptype { |
121 | for _, f := range pload.Fields { | 115 | for _, f := range pload.Fields { |
122 | fields = append(fields, f) | 116 | fields = append(fields, f) |
123 | } | 117 | } |
124 | } | 118 | } |
125 | } | 119 | } |
126 | 120 | ||
127 | return fields | 121 | return fields |
128 | } | 122 | } |
129 | 123 | ||
130 | // id returns the name of ID field of a payload/table of ptype type. | 124 | // id returns the name of ID field of a payload/table of ptype type. |
131 | func id(ptype string) string { | 125 | func id(ptype string) string { |
132 | for _, pload := range payloads { | 126 | for _, pload := range payloads { |
133 | if pload.Type == ptype { | 127 | if pload.Type == ptype { |
134 | return pload.IdField | 128 | return pload.IdField |
135 | } | 129 | } |
136 | } | 130 | } |
137 | return "" | 131 | return "" |
138 | } | 132 | } |
139 | 133 | ||
140 | // correlations returns a slice of correlation fields for a payload/table of ptype type. | 134 | // correlations returns a slice of correlation fields for a payload/table of ptype type. |
141 | func correlations(ptype string) []CorrelationField { | 135 | func correlations(ptype string) []CorrelationField { |
142 | var corr []CorrelationField | 136 | var corr []CorrelationField |
143 | 137 | ||
144 | for _, pload := range payloads { | 138 | for _, pload := range payloads { |
145 | if pload.Type == ptype { | 139 | if pload.Type == ptype { |
146 | for _, c := range pload.Correlations { | 140 | for _, c := range pload.Correlations { |
147 | corr = append(corr, c) | 141 | corr = append(corr, c) |
148 | } | 142 | } |
149 | } | 143 | } |
150 | } | 144 | } |
151 | 145 | ||
152 | return corr | 146 | return corr |
153 | } | 147 | } |
154 | 148 | ||
155 | // fetchJSON returns a byte slice of JSON configuration file from TABLES_CONFIG table. | 149 | // fetchJSON returns a byte slice of JSON configuration file from TABLES_CONFIG table. |
156 | // Returns an error if it fails. | 150 | // Returns an error if it fails. |
157 | func fetchJSON(db *ora.Ses, project string) ([]byte, error) { | 151 | func fetchJSON(db *ora.Ses, project string) ([]byte, error) { |
158 | db.SetCfg(db.Cfg().SetClob(ora.S)) | 152 | db.SetCfg(db.Cfg().SetClob(ora.S)) |
159 | stmt, err := db.Prep(`SELECT JSON_NCLOB FROM TABLES_CONFIG WHERE PROJEKAT = `+fmt.Sprintf("'%s'", project), ora.S) | 153 | stmt, err := db.Prep(`SELECT JSON_NCLOB FROM TABLES_CONFIG WHERE PROJEKAT = `+fmt.Sprintf("'%s'", project), ora.S) |
160 | defer stmt.Close() | 154 | defer stmt.Close() |
161 | if err != nil { | 155 | if err != nil { |
162 | return nil, err | 156 | return nil, err |
163 | } | 157 | } |
164 | 158 | ||
165 | rset, err := stmt.Qry() | 159 | rset, err := stmt.Qry() |
166 | if err != nil { | 160 | if err != nil { |
167 | return nil, err | 161 | return nil, err |
168 | } | 162 | } |
169 | 163 | ||
170 | var data string | 164 | var data string |
171 | if rset.Next() { | 165 | if rset.Next() { |
172 | data = rset.Row[0].(string) | 166 | data = rset.Row[0].(string) |
173 | } | 167 | } |
174 | 168 | ||
175 | //fmt.Println(data) | 169 | //fmt.Println(data) |
176 | return []byte(data), nil | 170 | return []byte(data), nil |
177 | } | 171 | } |
178 | 172 |