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