Commit 33d137a67174c4877300a83d5741f58c3f160c65
1 parent
3a5383589b
Exists in
master
and in
1 other branch
Functional role checking for API endpoints.
Showing
3 changed files
with
61 additions
and
9 deletions
Show diff stats
auth_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "errors" | 4 | "errors" |
5 | "time" | 5 | "time" |
6 | "crypto/sha256" | 6 | "crypto/sha256" |
7 | "crypto/rand" | 7 | "crypto/rand" |
8 | "encoding/hex" | 8 | "encoding/hex" |
9 | "strings" | 9 | "strings" |
10 | "net/http" | ||
11 | |||
10 | "github.com/dgrijalva/jwt-go" | 12 | "github.com/dgrijalva/jwt-go" |
13 | "fmt" | ||
11 | ) | 14 | ) |
12 | 15 | ||
13 | const OneDay = time.Hour*24 | 16 | const OneDay = time.Hour*24 |
14 | const OneWeek = OneDay*7 | 17 | const OneWeek = OneDay*7 |
15 | const saltSize = 32 | 18 | const saltSize = 32 |
16 | const appName = "korisnicki-centar" | 19 | const appName = "korisnicki-centar" |
17 | const secret = "korisnicki-centar-api" | 20 | const secret = "korisnicki-centar-api" |
18 | 21 | ||
22 | const RoleAdmin string = "ADMINISTRATOR" | ||
23 | const RoleManager string = "RUKOVODILAC" | ||
24 | const RoleReporter string = "REPORTER" | ||
25 | const RoleOperator string = "OPERATER" | ||
26 | |||
19 | // TokenClaims are JWT token claims. | 27 | // TokenClaims are JWT token claims. |
20 | type TokenClaims struct { | 28 | type TokenClaims struct { |
21 | Username string `json:"username"` | 29 | Username string `json:"username"` |
22 | Role string `json:"role"` | 30 | Role string `json:"role"` |
23 | jwt.StandardClaims | 31 | jwt.StandardClaims |
24 | } | 32 | } |
25 | 33 | ||
26 | // CredentialsStruct is an instace of username/password values. | 34 | // CredentialsStruct is an instace of username/password values. |
27 | type CredentialsStruct struct { | 35 | type CredentialsStruct struct { |
28 | Username string `json:"username"` | 36 | Username string `json:"username"` |
29 | Password string `json:"password"` | 37 | Password string `json:"password"` |
30 | } | 38 | } |
31 | 39 | ||
32 | // generateSalt returns a string of random characters of 'saltSize' length. | 40 | // generateSalt returns a string of random characters of 'saltSize' length. |
33 | func generateSalt() (salt string, err error) { | 41 | func generateSalt() (salt string, err error) { |
34 | rawsalt := make([]byte, saltSize) | 42 | rawsalt := make([]byte, saltSize) |
35 | 43 | ||
36 | _, err = rand.Read(rawsalt) | 44 | _, err = rand.Read(rawsalt) |
37 | if err != nil { | 45 | if err != nil { |
38 | return "", err | 46 | return "", err |
39 | } | 47 | } |
40 | 48 | ||
41 | salt = hex.EncodeToString(rawsalt) | 49 | salt = hex.EncodeToString(rawsalt) |
42 | return salt, nil | 50 | return salt, nil |
43 | } | 51 | } |
44 | 52 | ||
45 | // HashString hashes input string with SHA256 algorithm. | 53 | // HashString hashes input string with SHA256 algorithm. |
46 | // If the presalt parameter is not provided HashString will generate new salt string. | 54 | // If the presalt parameter is not provided HashString will generate new salt string. |
47 | // Returns hash and salt string or an error if it fails. | 55 | // Returns hash and salt string or an error if it fails. |
48 | func HashString(str string, presalt string) (hash, salt string, err error) { | 56 | func HashString(str string, presalt string) (hash, salt string, err error) { |
49 | // chech if message is presalted | 57 | // chech if message is presalted |
50 | if presalt == "" { | 58 | if presalt == "" { |
51 | salt, err = generateSalt() | 59 | salt, err = generateSalt() |
52 | if err != nil { | 60 | if err != nil { |
53 | return "", "", err | 61 | return "", "", err |
54 | } | 62 | } |
55 | } else { | 63 | } else { |
56 | salt = presalt | 64 | salt = presalt |
57 | } | 65 | } |
58 | 66 | ||
59 | // convert strings to raw byte slices | 67 | // convert strings to raw byte slices |
60 | rawstr := []byte(str) | 68 | rawstr := []byte(str) |
61 | rawsalt, err := hex.DecodeString(salt) | 69 | rawsalt, err := hex.DecodeString(salt) |
62 | if err != nil { | 70 | if err != nil { |
63 | return "", "", err | 71 | return "", "", err |
64 | } | 72 | } |
65 | 73 | ||
66 | rawdata := make([]byte, len(rawstr) + len(rawsalt)) | 74 | rawdata := make([]byte, len(rawstr) + len(rawsalt)) |
67 | rawdata = append(rawdata, rawstr...) | 75 | rawdata = append(rawdata, rawstr...) |
68 | rawdata = append(rawdata, rawsalt...) | 76 | rawdata = append(rawdata, rawsalt...) |
69 | 77 | ||
70 | // hash message + salt | 78 | // hash message + salt |
71 | hasher := sha256.New() | 79 | hasher := sha256.New() |
72 | hasher.Write(rawdata) | 80 | hasher.Write(rawdata) |
73 | rawhash := hasher.Sum(nil) | 81 | rawhash := hasher.Sum(nil) |
74 | 82 | ||
75 | hash = hex.EncodeToString(rawhash) | 83 | hash = hex.EncodeToString(rawhash) |
76 | return hash, salt, nil | 84 | return hash, salt, nil |
77 | } | 85 | } |
78 | 86 | ||
79 | // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims. | 87 | // CreateAPIToken returns JWT token with encoded username, role, expiration date and issuer claims. |
80 | // It returns an error if it fails. | 88 | // It returns an error if it fails. |
81 | func CreateAPIToken(username, role string) (string, error) { | 89 | func CreateAPIToken(username, role string) (string, error) { |
82 | var apiToken string | 90 | var apiToken string |
83 | var err error | 91 | var err error |
84 | 92 | ||
85 | if err != nil { | 93 | if err != nil { |
86 | return "", err | 94 | return "", err |
87 | } | 95 | } |
88 | 96 | ||
89 | claims := TokenClaims{ | 97 | claims := TokenClaims{ |
90 | username, | 98 | username, |
91 | role, | 99 | role, |
92 | jwt.StandardClaims{ | 100 | jwt.StandardClaims{ |
93 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), | 101 | ExpiresAt: (time.Now().Add(OneWeek)).Unix(), |
94 | Issuer: appName, | 102 | Issuer: appName, |
95 | }, | 103 | }, |
96 | } | 104 | } |
97 | 105 | ||
98 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 106 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
99 | apiToken, err = jwtToken.SignedString([]byte(secret)) | 107 | apiToken, err = jwtToken.SignedString([]byte(secret)) |
100 | if err != nil { | 108 | if err != nil { |
101 | return "", err | 109 | return "", err |
102 | } | 110 | } |
103 | return apiToken, nil | 111 | return apiToken, nil |
104 | } | 112 | } |
105 | 113 | ||
106 | // RefreshAPIToken prolongs JWT token's expiration date for one week. | 114 | // RefreshAPIToken prolongs JWT token's expiration date for one week. |
107 | // It returns new JWT token or an error if it fails. | 115 | // It returns new JWT token or an error if it fails. |
108 | func RefreshAPIToken(tokenString string) (string, error) { | 116 | func RefreshAPIToken(tokenString string) (string, error) { |
109 | var newToken string | 117 | var newToken string |
110 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 118 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
111 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) | 119 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) |
112 | if err != nil { | 120 | if err != nil { |
113 | return "", err | 121 | return "", err |
114 | } | 122 | } |
115 | 123 | ||
116 | // type assertion | 124 | // type assertion |
117 | claims, ok := token.Claims.(*TokenClaims) | 125 | claims, ok := token.Claims.(*TokenClaims) |
118 | if !ok || !token.Valid { | 126 | if !ok || !token.Valid { |
119 | return "", errors.New("token is not valid") | 127 | return "", errors.New("token is not valid") |
120 | } | 128 | } |
121 | 129 | ||
122 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() | 130 | claims.ExpiresAt = (time.Now().Add(OneWeek)).Unix() |
123 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | 131 | jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) |
124 | 132 | ||
125 | newToken, err = jwtToken.SignedString([]byte(secret)) | 133 | newToken, err = jwtToken.SignedString([]byte(secret)) |
126 | if err != nil { | 134 | if err != nil { |
127 | return "", err | 135 | return "", err |
128 | } | 136 | } |
129 | 137 | ||
130 | return newToken, nil | 138 | return newToken, nil |
131 | } | 139 | } |
132 | 140 | ||
133 | // ParseAPIToken parses JWT token claims. | 141 | // ParseAPIToken parses JWT token claims. |
134 | // It returns a pointer to TokenClaims struct or an error if it fails. | 142 | // It returns a pointer to TokenClaims struct or an error if it fails. |
135 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { | 143 | func ParseAPIToken(tokenString string) (*TokenClaims, error) { |
136 | if ok := strings.HasPrefix(tokenString, "Bearer "); ok { | 144 | if ok := strings.HasPrefix(tokenString, "Bearer "); ok { |
137 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") | 145 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") |
138 | } else { | 146 | } else { |
139 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | 147 | return &TokenClaims{}, errors.New("Authorization header is incomplete") |
140 | } | 148 | } |
141 | 149 | ||
142 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) | 150 | token, err := jwt.ParseWithClaims(tokenString, &TokenClaims{}, secretFunc) |
143 | if err != nil { | 151 | if err != nil { |
144 | return &TokenClaims{}, err | 152 | return &TokenClaims{}, err |
145 | } | 153 | } |
146 | 154 | ||
147 | // type assertion | 155 | // type assertion |
148 | claims, ok := token.Claims.(*TokenClaims) | 156 | claims, ok := token.Claims.(*TokenClaims) |
149 | if !ok || !token.Valid { | 157 | if !ok || !token.Valid { |
150 | return &TokenClaims{}, errors.New("token is not valid") | 158 | return &TokenClaims{}, errors.New("token is not valid") |
151 | } | 159 | } |
152 | return claims, nil | 160 | return claims, nil |
153 | } | 161 | } |
154 | 162 | ||
163 | func GetTokenClaims(r *http.Request) (claims *TokenClaims, err error) { | ||
164 | token := r.Header.Get("Authorization") | ||
165 | if ok := strings.HasPrefix(token, "Bearer "); ok { | ||
166 | token = strings.TrimPrefix(token, "Bearer ") | ||
167 | } else { | ||
168 | return &TokenClaims{}, errors.New("Authorization header is incomplete") | ||
169 | } | ||
170 | |||
171 | parsedToken, err := jwt.ParseWithClaims(token, &TokenClaims{}, secretFunc) | ||
172 | if err != nil { | ||
173 | return &TokenClaims{}, err | ||
174 | } | ||
175 | |||
176 | // type assertion | ||
177 | claims, ok := parsedToken.Claims.(*TokenClaims) | ||
178 | if !ok || !parsedToken.Valid { | ||
179 | return &TokenClaims{}, errors.New("token is not valid") | ||
180 | } | ||
181 | return claims, err | ||
182 | } | ||
183 | |||
155 | // secretFunc returns byte slice of API secret keyword. | 184 | // secretFunc returns byte slice of API secret keyword. |
156 | func secretFunc(token *jwt.Token) (interface{}, error) { | 185 | func secretFunc(token *jwt.Token) (interface{}, error) { |
157 | return []byte(secret), nil | 186 | return []byte(secret), nil |
158 | } | 187 | } |
188 | |||
189 | // roleAuthorized returns true if role from userClaims matches any of the authorizedRoles | ||
190 | // or if authorizedRoles contains "*". | ||
191 | func roleAuthorized(authorizedRoles []string, userClaims *TokenClaims) bool { | ||
192 | if userClaims == nil { | ||
193 | return false | ||
194 | } | ||
195 | for _, r := range authorizedRoles { | ||
196 | fmt.Printf("comparing %s with %s\n", userClaims.Role, r) | ||
197 | if userClaims.Role == r || r == "*" { | ||
198 | return true | ||
199 | } | ||
200 | } | ||
201 | return false | ||
202 | } | ||
159 | 203 |
http_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "net/http" | 4 | "net/http" |
5 | "encoding/json" | 5 | "encoding/json" |
6 | "fmt" | ||
6 | ) | 7 | ) |
7 | 8 | ||
8 | const templateHttpErr500_EN = "An internal server error has occurred." | 9 | const templateHttpErr500_EN = "An internal server error has occurred." |
9 | const templateHttpErr500_RS = "Došlo je do greške na serveru." | 10 | const templateHttpErr500_RS = "Došlo je do greške na serveru." |
10 | const templateHttpErr400_EN = "Bad request: invalid request body." | 11 | const templateHttpErr400_EN = "Bad request: invalid request body." |
11 | const templateHttpErr400_RS = "Neispravan zahtev." | 12 | const templateHttpErr400_RS = "Neispravan zahtev." |
12 | const templateHttpErr401_EN = "Unauthorized request." | 13 | const templateHttpErr401_EN = "Unauthorized request." |
13 | const templateHttpErr401_RS = "Neautorizovan zahtev." | 14 | const templateHttpErr401_RS = "Neautorizovan zahtev." |
14 | 15 | ||
15 | type httpError struct { | 16 | type httpError struct { |
16 | Error []HttpErrorDesc `json:"error"` | 17 | Error []HttpErrorDesc `json:"error"` |
17 | Request string `json:"request"` | 18 | Request string `json:"request"` |
18 | } | 19 | } |
19 | 20 | ||
20 | type HttpErrorDesc struct { | 21 | type HttpErrorDesc struct { |
21 | Lang string `json:"lang"` | 22 | Lang string `json:"lang"` |
22 | Desc string `json:"description"` | 23 | Desc string `json:"description"` |
23 | } | 24 | } |
24 | 25 | ||
25 | // ErrorResponse writes HTTP error to w. | 26 | // ErrorResponse writes HTTP error to w. |
26 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { | 27 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { |
27 | err := httpError{ desc, r.Method + " " + r.URL.Path } | 28 | err := httpError{ desc, r.Method + " " + r.URL.Path } |
28 | w.WriteHeader(code) | 29 | w.WriteHeader(code) |
29 | json.NewEncoder(w).Encode(err) | 30 | json.NewEncoder(w).Encode(err) |
30 | } | 31 | } |
31 | 32 | ||
32 | // BadRequestResponse writes HTTP error 400 to w. | 33 | // BadRequestResponse writes HTTP error 400 to w. |
33 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { | 34 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { |
34 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ | 35 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ |
35 | { "en", templateHttpErr400_EN }, | 36 | { "en", templateHttpErr400_EN }, |
36 | { "rs", templateHttpErr400_RS }, | 37 | { "rs", templateHttpErr400_RS }, |
37 | }) | 38 | }) |
38 | } | 39 | } |
39 | 40 | ||
40 | // InternalSeverErrorResponse writes HTTP error 500 to w. | 41 | // InternalSeverErrorResponse writes HTTP error 500 to w. |
41 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { | 42 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { |
42 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ | 43 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ |
43 | { "en", templateHttpErr500_EN }, | 44 | { "en", templateHttpErr500_EN }, |
44 | { "rs", templateHttpErr500_RS }, | 45 | { "rs", templateHttpErr500_RS }, |
45 | }) | 46 | }) |
46 | } | 47 | } |
47 | 48 | ||
48 | // UnauthorizedError writes HTTP error 401 to w. | 49 | // UnauthorizedError writes HTTP error 401 to w. |
49 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { | 50 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { |
50 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ | 51 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ |
51 | { "en", templateHttpErr401_EN }, | 52 | { "en", templateHttpErr401_EN }, |
52 | { "rs", templateHttpErr401_RS }, | 53 | { "rs", templateHttpErr401_RS }, |
53 | }) | 54 | }) |
54 | } | 55 | } |
55 | 56 | ||
56 | // TODO: Check for content type | 57 | // TODO: Check for content type |
57 | // WrapHandler sets common headers, checks for token validity and performs access control. | 58 | // WrapHandler sets common headers, checks for token validity and performs access control checks. |
58 | // If authentication passes it calls the handlerFunc. | 59 | // If authentication passes it calls the handlerFunc. |
59 | func WrapHandler(handlerFunc http.HandlerFunc, auth bool) http.HandlerFunc { | 60 | func WrapHandler(handlerFunc http.HandlerFunc, authorizedRoles []string) http.HandlerFunc { |
60 | return func(w http.ResponseWriter, req *http.Request) { | 61 | return func(w http.ResponseWriter, req *http.Request) { |
61 | w.Header().Set("Access-Control-Allow-Origin", "*") | 62 | w.Header().Set("Access-Control-Allow-Origin", "*") |
62 | 63 | ||
63 | w.Header().Set("Access-Control-Allow-Methods", | 64 | w.Header().Set("Access-Control-Allow-Methods", |
64 | "POST, GET, PUT, DELETE, OPTIONS") | 65 | "POST, GET, PUT, DELETE, OPTIONS") |
65 | 66 | ||
66 | w.Header().Set("Access-Control-Allow-Headers", | 67 | w.Header().Set("Access-Control-Allow-Headers", |
67 | `Accept, Content-Type, Content-Length, | 68 | `Accept, Content-Type, Content-Length, |
68 | Accept-Encoding, X-CSRF-Token, Authorization`) | 69 | Accept-Encoding, X-CSRF-Token, Authorization`) |
69 | 70 | ||
70 | w.Header().Set("Content-Type", "application/json; charset=utf-8") | 71 | w.Header().Set("Content-Type", "application/json; charset=utf-8") |
71 | 72 | ||
72 | if req.Method == "OPTIONS" { | 73 | if req.Method == "OPTIONS" { |
73 | return | 74 | return |
74 | } | 75 | } |
75 | 76 | ||
76 | if auth { | 77 | if authorizedRoles != nil { |
77 | token := req.Header.Get("Authorization") | 78 | token := req.Header.Get("Authorization") |
78 | if _, err := ParseAPIToken(token); err != nil { | 79 | claims, err := ParseAPIToken(token); |
80 | if err != nil || !roleAuthorized(authorizedRoles, claims) { | ||
81 | fmt.Printf("not authorized %s %s...\n", claims.Username, claims.Role) | ||
79 | UnauthorizedResponse(w, req) | 82 | UnauthorizedResponse(w, req) |
80 | return | 83 | return |
81 | } | 84 | } |
82 | } | 85 | } |
83 | 86 | ||
84 | err := req.ParseForm() | 87 | err := req.ParseForm() |
85 | if err != nil { | 88 | if err != nil { |
86 | BadRequestResponse(w, req) | 89 | BadRequestResponse(w, req) |
87 | return | 90 | return |
88 | } | 91 | } |
89 | 92 | ||
90 | // execute HandlerFunc | 93 | // execute HandlerFunc |
91 | handlerFunc(w, req) | 94 | handlerFunc(w, req) |
92 | } | 95 | } |
93 | } | 96 | } |
94 | 97 | ||
95 | // NotFoundHandler writes HTTP error 404 to w. | 98 | // NotFoundHandler writes HTTP error 404 to w. |
96 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { | 99 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { |
97 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ | 100 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ |
98 | { "en", "Not found." }, | 101 | { "en", "Not found." }, |
99 | { "rs", "Traženi resurs ne postoji." }, | 102 | { "rs", "Traženi resurs ne postoji." }, |
100 | }) | 103 | }) |
101 | } | 104 | } |
102 | 105 |
json_utility.go
1 | package webutility | 1 | package webutility |
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "net/http" | 4 | "net/http" |
5 | "encoding/json" | 5 | "encoding/json" |
6 | "errors" | 6 | "errors" |
7 | "gopkg.in/rana/ora.v3" | 7 | "gopkg.in/rana/ora.v3" |
8 | "io" | 8 | "io" |
9 | "io/ioutil" | 9 | "io/ioutil" |
10 | "sync" | 10 | "sync" |
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 can only hold slices of any type. It can't be used for itteration | 57 | // Data can only hold slices of any type. It can't be used for itteration |
58 | Data interface{} `json:"data"` | 58 | Data interface{} `json:"data"` |
59 | } | 59 | } |
60 | 60 | ||
61 | // NewPayload returs a payload sceleton for provided table. | 61 | // NewPayload returs a payload sceleton for provided table. |
62 | func NewPayload(r *http.Request, table string) Payload { | 62 | func NewPayload(r *http.Request, table string) Payload { |
63 | var pload Payload | 63 | var pload Payload |
64 | 64 | ||
65 | pload.Method = r.Method + " " + r.URL.Path | 65 | pload.Method = r.Method + " " + r.URL.Path |
66 | if table != "" { | 66 | if table != "" { |
67 | pload.Params = make(map[string]string, 0) | 67 | pload.Params = make(map[string]string, 0) |
68 | pload.Lang = translations(table) | 68 | pload.Lang = translations(table) |
69 | pload.Fields = fields(table) | 69 | pload.Fields = fields(table) |
70 | pload.IdField = id(table) | 70 | pload.IdField = id(table) |
71 | pload.Correlations = correlations(table) | 71 | pload.Correlations = correlations(table) |
72 | } | 72 | } |
73 | return pload | 73 | return pload |
74 | } | 74 | } |
75 | 75 | ||
76 | // DeliverPayload encodes payload to w. | 76 | // DeliverPayload encodes payload to w. |
77 | func DeliverPayload(w http.ResponseWriter, payload Payload) { | 77 | func DeliverPayload(w http.ResponseWriter, payload Payload) { |
78 | json.NewEncoder(w).Encode(payload) | 78 | json.NewEncoder(w).Encode(payload) |
79 | payload.Data = nil | 79 | payload.Data = nil |
80 | } | 80 | } |
81 | 81 | ||
82 | // translations returns a slice of translations for a payload/table of ptype type. | 82 | // translations returns a slice of translations for a payload/table of ptype type. |
83 | func translations(ptype string) []Translation { | 83 | func translations(ptype string) []Translation { |
84 | var translations []Translation | 84 | var translations []Translation |
85 | 85 | ||
86 | for _, pload := range payloads { | 86 | for _, pload := range payloads { |
87 | if pload.Type == ptype { | 87 | if pload.Type == ptype { |
88 | for _, t := range pload.Lang { | 88 | for _, t := range pload.Lang { |
89 | translations = append(translations, Translation{ | 89 | translations = append(translations, Translation{ |
90 | Language: t.Language, | 90 | Language: t.Language, |
91 | FieldsLabels: t.FieldsLabels, | 91 | FieldsLabels: t.FieldsLabels, |
92 | }) | 92 | }) |
93 | } | 93 | } |
94 | } | 94 | } |
95 | } | 95 | } |
96 | 96 | ||
97 | return translations | 97 | return translations |
98 | } | 98 | } |
99 | 99 | ||
100 | // fields returns a slice of fields for a payload/table of ptype type. | 100 | // fields returns a slice of fields for a payload/table of ptype type. |
101 | func fields(ptype string) []Field { | 101 | func fields(ptype string) []Field { |
102 | var fields []Field | 102 | var fields []Field |
103 | 103 | ||
104 | for _, pload := range payloads { | 104 | for _, pload := range payloads { |
105 | if pload.Type == ptype { | 105 | if pload.Type == ptype { |
106 | for _, f := range pload.Fields { | 106 | for _, f := range pload.Fields { |
107 | fields = append(fields, f) | 107 | fields = append(fields, f) |
108 | } | 108 | } |
109 | } | 109 | } |
110 | } | 110 | } |
111 | 111 | ||
112 | return fields | 112 | return fields |
113 | } | 113 | } |
114 | 114 | ||
115 | // id returns the name of ID field of a payload/table of ptype type. | 115 | // id returns the name of ID field of a payload/table of ptype type. |
116 | func id(ptype string) string { | 116 | func id(ptype string) string { |
117 | for _, pload := range payloads { | 117 | for _, pload := range payloads { |
118 | if pload.Type == ptype { | 118 | if pload.Type == ptype { |
119 | return pload.IdField | 119 | return pload.IdField |
120 | } | 120 | } |
121 | } | 121 | } |
122 | return "" | 122 | return "" |
123 | } | 123 | } |
124 | 124 | ||
125 | // correlations returns a slice of correlation fields for a payload/table of ptype type. | 125 | // correlations returns a slice of correlation fields for a payload/table of ptype type. |
126 | func correlations(ptype string) []CorrelationField { | 126 | func correlations(ptype string) []CorrelationField { |
127 | var corr []CorrelationField | 127 | var corr []CorrelationField |
128 | 128 | ||
129 | for _, pload := range payloads { | 129 | for _, pload := range payloads { |
130 | if pload.Type == ptype { | 130 | if pload.Type == ptype { |
131 | for _, c := range pload.Correlations { | 131 | for _, c := range pload.Correlations { |
132 | corr = append(corr, c) | 132 | corr = append(corr, c) |
133 | } | 133 | } |
134 | } | 134 | } |
135 | } | 135 | } |
136 | 136 | ||
137 | return corr | 137 | return corr |
138 | } | 138 | } |
139 | 139 | ||
140 | // InitTables loads all payloads in the payloads variable. | 140 | // InitTables loads all payloads in the payloads variable. |
141 | // Returns an error if it fails. | 141 | // Returns an error if it fails. |
142 | func InitTables(db *ora.Ses, project string) error { | 142 | func InitTables(db *ora.Ses, project string) error { |
143 | jsonbuf, _ := fetchJSON(db, EqualQuotes(project)) | 143 | jsonbuf, _ := fetchJSON(db, EqualQuotes(project)) |
144 | mu.Lock() | 144 | mu.Lock() |
145 | defer mu.Unlock() | 145 | defer mu.Unlock() |
146 | json.Unmarshal(jsonbuf, &payloads) | 146 | json.Unmarshal(jsonbuf, &payloads) |
147 | if len(payloads) == 0 { | 147 | if len(payloads) == 0 { |
148 | return errors.New("tables config is corrupt") | 148 | return errors.New("tables config is corrupt") |
149 | } | 149 | } |
150 | return nil | 150 | return nil |
151 | } | 151 | } |
152 | 152 | ||
153 | // fetchJSON returns a byte slice of JSON configuration file from TABLES_CONFIG table. | 153 | // fetchJSON returns a byte slice of JSON configuration file from TABLES_CONFIG table. |
154 | // Returns an error if it fails. | 154 | // Returns an error if it fails. |
155 | func fetchJSON(db *ora.Ses, project string) ([]byte, error) { | 155 | func fetchJSON(db *ora.Ses, project string) ([]byte, error) { |
156 | stmt, err := db.Prep(`SELECT | 156 | stmt, err := db.Prep(`SELECT |
157 | JSON_CLOB | 157 | JSON_CLOB |
158 | FROM TABLES_CONFIG | 158 | FROM TABLES_CONFIG |
159 | WHERE PROJEKAT` + project, ora.S) | 159 | WHERE PROJEKAT` + project, ora.S) |
160 | defer stmt.Close() | 160 | defer stmt.Close() |
161 | 161 | ||
162 | if err != nil { | 162 | if err != nil { |
163 | return nil, err | 163 | return nil, err |
164 | } | 164 | } |
165 | 165 | ||
166 | rset, err := stmt.Qry() | 166 | rset, err := stmt.Qry() |
167 | if err != nil { | 167 | if err != nil { |
168 | return nil, err | 168 | return nil, err |
169 | } | 169 | } |
170 | 170 | ||
171 | bytes := make([]byte, 0) | 171 | bytes := make([]byte, 0) |
172 | if rset.Next() { | 172 | if rset.Next() { |
173 | lob := rset.Row[0].(io.Reader) | 173 | lob := rset.Row[0].(io.Reader) |
174 | bytes, err = ioutil.ReadAll(lob) | 174 | if lob != nil { |
175 | if err != nil { | 175 | bytes, err = ioutil.ReadAll(lob) |
176 | // Ignore for now, it's some weird streaming read/write LOB error. | 176 | if err != nil { |
177 | // TODO: Find a fix for this. | 177 | // TODO: Find a fix for this. |
178 | //return nil, err | 178 | // Some weird streaming read/write LOB error. |
179 | // Ignore for now. | ||
180 | //return nil, err | ||
181 | } | ||
182 | } else { | ||
183 | return nil, errors.New("json config is null") | ||
179 | } | 184 | } |
180 | } | 185 | } |
181 | 186 | ||
182 | return bytes, nil | 187 | return bytes, nil |
183 | } | 188 | } |
184 | 189 | ||
185 | // DecodeJSON decodes JSON data from r to v. | 190 | // DecodeJSON decodes JSON data from r to v. |
186 | // Returns an error if it fails. | 191 | // Returns an error if it fails. |
187 | func DecodeJSON(r io.Reader, v interface{}) error { | 192 | func DecodeJSON(r io.Reader, v interface{}) error { |
188 | return json.NewDecoder(r).Decode(v) | 193 | return json.NewDecoder(r).Decode(v) |
189 | } | 194 | } |
190 | 195 |