Commit d2ddf82ef16c3ab5764f1b8136f1f387b117277b
1 parent
6b16c88f25
Exists in
master
and in
1 other branch
started on new rbac
Showing
4 changed files
with
131 additions
and
93 deletions
Show diff stats
auth_utility.go
1 | +// TODO: Improve roles | |
1 | 2 | package webutility |
2 | 3 | |
3 | 4 | import ( |
4 | - "errors" | |
5 | - "time" | |
6 | - "crypto/sha256" | |
7 | 5 | "crypto/rand" |
6 | + "crypto/sha256" | |
8 | 7 | "encoding/hex" |
9 | - "strings" | |
8 | + "errors" | |
10 | 9 | "net/http" |
10 | + "strings" | |
11 | + "time" | |
11 | 12 | |
12 | 13 | "github.com/dgrijalva/jwt-go" |
13 | 14 | ) |
14 | 15 | |
15 | -const OneDay = time.Hour*24 | |
16 | -const OneWeek = OneDay*7 | |
16 | +const OneDay = time.Hour * 24 | |
17 | +const OneWeek = OneDay * 7 | |
17 | 18 | const saltSize = 32 |
18 | -const appName = "korisnicki-centar" | |
19 | -const secret = "korisnicki-centar-api" | |
19 | +const appName = "korisnicki-centar" | |
20 | +const secret = "korisnicki-centar-api" | |
20 | 21 | |
21 | -const RoleAdmin string = "ADMINISTRATOR" | |
22 | -const RoleManager string = "RUKOVODILAC" | |
22 | +const RoleAdmin string = "ADMINISTRATOR" | |
23 | +const RoleManager string = "RUKOVODILAC" | |
23 | 24 | const RoleReporter string = "REPORTER" |
24 | 25 | const RoleOperator string = "OPERATER" |
25 | -const RoleAdminID uint32 = 1 | |
26 | -const RoleManagerID uint32 = 2 | |
26 | +const RoleAdminID uint32 = 1 | |
27 | +const RoleManagerID uint32 = 2 | |
27 | 28 | const RoleReporterID uint32 = 3 |
28 | 29 | const RoleOperatorID uint32 = 4 |
29 | 30 | |
31 | +type Role struct { | |
32 | + name string | |
33 | + id uint32 | |
34 | +} | |
35 | + | |
30 | 36 | // TokenClaims are JWT token claims. |
31 | 37 | type TokenClaims struct { |
32 | 38 | Username string `json:"username"` |
... | ... | @@ -41,6 +47,11 @@ type CredentialsStruct struct { |
41 | 47 | Password string `json:"password"` |
42 | 48 | } |
43 | 49 | |
50 | +var admin Role = Role{RoleAdmin, RoleAdminID} | |
51 | +var manager Role = Role{RoleManager, RoleManagerID} | |
52 | +var reporter Role = Role{RoleReporter, RoleReporterID} | |
53 | +var operator Role = Role{RoleOperator, RoleOperatorID} | |
54 | + | |
44 | 55 | // generateSalt returns a string of random characters of 'saltSize' length. |
45 | 56 | func generateSalt() (salt string, err error) { |
46 | 57 | rawsalt := make([]byte, saltSize) |
... | ... | @@ -75,7 +86,7 @@ func HashString(str string, presalt string) (hash, salt string, err error) { |
75 | 86 | return "", "", err |
76 | 87 | } |
77 | 88 | |
78 | - rawdata := make([]byte, len(rawstr) + len(rawsalt)) | |
89 | + rawdata := make([]byte, len(rawstr)+len(rawsalt)) | |
79 | 90 | rawdata = append(rawdata, rawstr...) |
80 | 91 | rawdata = append(rawdata, rawsalt...) |
81 | 92 | |
... | ... | @@ -191,16 +202,63 @@ func secretFunc(token *jwt.Token) (interface{}, error) { |
191 | 202 | return []byte(secret), nil |
192 | 203 | } |
193 | 204 | |
194 | -// roleAuthorized returns true if role from userClaims matches any of the authorizedRoles | |
195 | -// or if authorizedRoles contains "*". | |
196 | -func roleAuthorized(authorizedRoles []string, userClaims *TokenClaims) bool { | |
197 | - if userClaims == nil { | |
205 | +// rbacEnforce returns true if role that made HTTP request is authorized to | |
206 | +// access the resource it is targeting. | |
207 | +// It exctracts user's role from the JWT token located in Authorization header of | |
208 | +// http.Request and then compares it with the list of supplied roles and returns | |
209 | +// true if there's a match, if "*" is provided or if the authRoles is nil. | |
210 | +// Otherwise it returns false. | |
211 | +func RbacCheck(req *http.Request, authRoles []string) bool { | |
212 | + if authRoles == nil { | |
213 | + return true | |
214 | + } | |
215 | + | |
216 | + token := req.Header.Get("Authorization") | |
217 | + claims, err := ParseAPIToken(token) | |
218 | + if err != nil { | |
198 | 219 | return false |
199 | 220 | } |
200 | - for _, r := range authorizedRoles { | |
201 | - if userClaims.Role == r || r == "*" { | |
221 | + | |
222 | + for _, r := range authRoles { | |
223 | + if claims.Role == r || r == "*" { | |
202 | 224 | return true |
203 | 225 | } |
204 | 226 | } |
227 | + | |
205 | 228 | return false |
206 | 229 | } |
230 | + | |
231 | +// Rbac sets common headers and performs RBAC. | |
232 | +// If RBAC passes it calls the handlerFunc. | |
233 | +func Rbac(handlerFunc http.HandlerFunc, authRoles []string) http.HandlerFunc { | |
234 | + return func(w http.ResponseWriter, req *http.Request) { | |
235 | + w.Header().Set("Access-Control-Allow-Origin", "*") | |
236 | + | |
237 | + w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") | |
238 | + | |
239 | + w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, | |
240 | + Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) | |
241 | + | |
242 | + w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
243 | + | |
244 | + // TODO: Check for content type | |
245 | + | |
246 | + if req.Method == "OPTIONS" { | |
247 | + return | |
248 | + } | |
249 | + | |
250 | + err := req.ParseForm() | |
251 | + if err != nil { | |
252 | + BadRequestResponse(w, req) | |
253 | + return | |
254 | + } | |
255 | + | |
256 | + if !RbacCheck(req, authRoles) { | |
257 | + UnauthorizedResponse(w, req) | |
258 | + return | |
259 | + } | |
260 | + | |
261 | + // execute HandlerFunc | |
262 | + handlerFunc(w, req) | |
263 | + } | |
264 | +} | ... | ... |
http_utility.go
1 | 1 | package webutility |
2 | 2 | |
3 | 3 | import ( |
4 | - "net/http" | |
5 | 4 | "encoding/json" |
5 | + "net/http" | |
6 | 6 | ) |
7 | 7 | |
8 | 8 | const templateHttpErr500_EN = "An internal server error has occurred." |
... | ... | @@ -24,7 +24,7 @@ type HttpErrorDesc struct { |
24 | 24 | |
25 | 25 | // ErrorResponse writes HTTP error to w. |
26 | 26 | func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []HttpErrorDesc) { |
27 | - err := httpError{ desc, r.Method + " " + r.URL.Path } | |
27 | + err := httpError{desc, r.Method + " " + r.URL.Path} | |
28 | 28 | w.WriteHeader(code) |
29 | 29 | json.NewEncoder(w).Encode(err) |
30 | 30 | } |
... | ... | @@ -32,71 +32,46 @@ func ErrorResponse(w http.ResponseWriter, r *http.Request, code int, desc []Http |
32 | 32 | // BadRequestResponse writes HTTP error 400 to w. |
33 | 33 | func BadRequestResponse(w http.ResponseWriter, req *http.Request) { |
34 | 34 | ErrorResponse(w, req, http.StatusBadRequest, []HttpErrorDesc{ |
35 | - { "en", templateHttpErr400_EN }, | |
36 | - { "rs", templateHttpErr400_RS }, | |
35 | + {"en", templateHttpErr400_EN}, | |
36 | + {"rs", templateHttpErr400_RS}, | |
37 | 37 | }) |
38 | 38 | } |
39 | 39 | |
40 | 40 | // InternalSeverErrorResponse writes HTTP error 500 to w. |
41 | 41 | func InternalServerErrorResponse(w http.ResponseWriter, req *http.Request) { |
42 | 42 | ErrorResponse(w, req, http.StatusInternalServerError, []HttpErrorDesc{ |
43 | - { "en", templateHttpErr500_EN }, | |
44 | - { "rs", templateHttpErr500_RS }, | |
43 | + {"en", templateHttpErr500_EN}, | |
44 | + {"rs", templateHttpErr500_RS}, | |
45 | 45 | }) |
46 | 46 | } |
47 | 47 | |
48 | 48 | // UnauthorizedError writes HTTP error 401 to w. |
49 | 49 | func UnauthorizedResponse(w http.ResponseWriter, req *http.Request) { |
50 | 50 | ErrorResponse(w, req, http.StatusUnauthorized, []HttpErrorDesc{ |
51 | - { "en", templateHttpErr401_EN }, | |
52 | - { "rs", templateHttpErr401_RS }, | |
51 | + {"en", templateHttpErr401_EN}, | |
52 | + {"rs", templateHttpErr401_RS}, | |
53 | 53 | }) |
54 | 54 | } |
55 | 55 | |
56 | -// TODO: Check for content type | |
57 | -// WrapHandler sets common headers, checks for token validity and performs access control checks. | |
58 | -// If authentication passes it calls the handlerFunc. | |
59 | -func WrapHandler(handlerFunc http.HandlerFunc, authorizedRoles []string) http.HandlerFunc { | |
60 | - return func(w http.ResponseWriter, req *http.Request) { | |
61 | - w.Header().Set("Access-Control-Allow-Origin", "*") | |
62 | - | |
63 | - w.Header().Set("Access-Control-Allow-Methods", | |
64 | - "POST, GET, PUT, DELETE, OPTIONS") | |
65 | - | |
66 | - w.Header().Set("Access-Control-Allow-Headers", | |
67 | - `Accept, Content-Type, Content-Length, | |
68 | - Accept-Encoding, X-CSRF-Token, Authorization`) | |
69 | - | |
70 | - w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
71 | - | |
72 | - if req.Method == "OPTIONS" { | |
73 | - return | |
74 | - } | |
75 | - | |
76 | - if authorizedRoles != nil { | |
77 | - token := req.Header.Get("Authorization") | |
78 | - claims, err := ParseAPIToken(token); | |
79 | - if err != nil || !roleAuthorized(authorizedRoles, claims) { | |
80 | - UnauthorizedResponse(w, req) | |
81 | - return | |
82 | - } | |
83 | - } | |
84 | - | |
85 | - err := req.ParseForm() | |
86 | - if err != nil { | |
87 | - BadRequestResponse(w, req) | |
88 | - return | |
89 | - } | |
90 | - | |
91 | - // execute HandlerFunc | |
92 | - handlerFunc(w, req) | |
93 | - } | |
94 | -} | |
95 | - | |
96 | 56 | // NotFoundHandler writes HTTP error 404 to w. |
97 | 57 | func NotFoundHandler(w http.ResponseWriter, req *http.Request) { |
58 | + SetDefaultHeaders(w) | |
59 | + if req.Method == "OPTIONS" { | |
60 | + return | |
61 | + } | |
98 | 62 | ErrorResponse(w, req, http.StatusNotFound, []HttpErrorDesc{ |
99 | - { "en", "Not found." }, | |
100 | - { "rs", "Traženi resurs ne postoji." }, | |
63 | + {"en", "Not found."}, | |
64 | + {"rs", "Traženi resurs ne postoji."}, | |
101 | 65 | }) |
102 | 66 | } |
67 | + | |
68 | +func SetDefaultHeaders(w http.ResponseWriter) { | |
69 | + w.Header().Set("Access-Control-Allow-Origin", "*") | |
70 | + | |
71 | + w.Header().Set("Access-Control-Allow-Methods", "POST, GET, PUT, DELETE, OPTIONS") | |
72 | + | |
73 | + w.Header().Set("Access-Control-Allow-Headers", `Accept, Content-Type, | |
74 | + Content-Length, Accept-Encoding, X-CSRF-Token, Authorization`) | |
75 | + | |
76 | + w.Header().Set("Content-Type", "application/json; charset=utf-8") | |
77 | +} | ... | ... |
json_utility.go
... | ... | @@ -2,10 +2,10 @@ package webutility |
2 | 2 | |
3 | 3 | import ( |
4 | 4 | //"fmt" |
5 | - "net/http" | |
6 | 5 | "encoding/json" |
7 | 6 | "errors" |
8 | 7 | "io" |
8 | + "net/http" | |
9 | 9 | //"io/ioutil" |
10 | 10 | "sync" |
11 | 11 | |
... | ... | @@ -19,10 +19,10 @@ var payloads []payloadBuff |
19 | 19 | type LangMap map[string]map[string]string |
20 | 20 | |
21 | 21 | type Field struct { |
22 | - Parameter string `json:"param"` | |
23 | - Type string `json:"type"` | |
24 | - Visible bool `json:"visible"` | |
25 | - Editable bool `json:"editable"` | |
22 | + Parameter string `json:"param"` | |
23 | + Type string `json:"type"` | |
24 | + Visible bool `json:"visible"` | |
25 | + Editable bool `json:"editable"` | |
26 | 26 | } |
27 | 27 | |
28 | 28 | type CorrelationField struct { |
... | ... | @@ -38,27 +38,27 @@ type Translation struct { |
38 | 38 | |
39 | 39 | type payloadBuff struct { |
40 | 40 | Type string `json:"tableType"` |
41 | - Method string `json:"method"` | |
42 | - Params map[string]string `json:"params"` | |
43 | - Lang []Translation `json:"lang"` | |
44 | - Fields []Field `json:"fields"` | |
41 | + Method string `json:"method"` | |
42 | + Params map[string]string `json:"params"` | |
43 | + Lang []Translation `json:"lang"` | |
44 | + Fields []Field `json:"fields"` | |
45 | 45 | Correlations []CorrelationField `json:"correlationFields"` |
46 | 46 | IdField string `json:"idField"` |
47 | 47 | |
48 | 48 | // Data can only hold slices of any type. It can't be used for itteration |
49 | - Data interface{} `json:"data"` | |
49 | + Data interface{} `json:"data"` | |
50 | 50 | } |
51 | 51 | |
52 | 52 | type Payload struct { |
53 | - Method string `json:"method"` | |
54 | - Params map[string]string `json:"params"` | |
55 | - Lang []Translation `json:"lang"` | |
56 | - Fields []Field `json:"fields"` | |
53 | + Method string `json:"method"` | |
54 | + Params map[string]string `json:"params"` | |
55 | + Lang []Translation `json:"lang"` | |
56 | + Fields []Field `json:"fields"` | |
57 | 57 | Correlations []CorrelationField `json:"correlationFields"` |
58 | 58 | IdField string `json:"idField"` |
59 | 59 | |
60 | 60 | // Data can only hold slices of any type. It can't be used for itteration |
61 | - Data interface{} `json:"data"` | |
61 | + Data interface{} `json:"data"` | |
62 | 62 | } |
63 | 63 | |
64 | 64 | // NewPayload returs a payload sceleton for provided table. |
... | ... | @@ -90,7 +90,7 @@ func translations(ptype string) []Translation { |
90 | 90 | if pload.Type == ptype { |
91 | 91 | for _, t := range pload.Lang { |
92 | 92 | translations = append(translations, Translation{ |
93 | - Language: t.Language, | |
93 | + Language: t.Language, | |
94 | 94 | FieldsLabels: t.FieldsLabels, |
95 | 95 | }) |
96 | 96 | } |
... | ... | @@ -161,7 +161,7 @@ func InitTables(db *ora.Ses, project string) error { |
161 | 161 | // Returns an error if it fails. |
162 | 162 | func fetchJSON(db *ora.Ses, project string) ([]byte, error) { |
163 | 163 | db.SetCfg(db.Cfg().SetClob(ora.S)) |
164 | - stmt, err := db.Prep(`SELECT JSON_NCLOB FROM TABLES_CONFIG WHERE PROJEKAT` + EqualQuotes(project), ora.S) | |
164 | + stmt, err := db.Prep(`SELECT JSON_NCLOB FROM TABLES_CONFIG WHERE PROJEKAT`+EqualQuotes(project), ora.S) | |
165 | 165 | defer stmt.Close() |
166 | 166 | if err != nil { |
167 | 167 | return nil, err | ... | ... |
select_config.go
... | ... | @@ -17,8 +17,13 @@ func GetSelectConfig(db *ora.Ses, otype string) ([]SelectConfig, error) { |
17 | 17 | resp := make([]SelectConfig, 0) |
18 | 18 | var err error |
19 | 19 | var stmt *ora.Stmt |
20 | - query := `SELECT a.LIST_OBJECT_TYPE, a.OBJECT_TYPE, a.ID_FIELD, | |
21 | - a.LABEL_FIELD, a.TYPE, b.FIELD | |
20 | + query := `SELECT | |
21 | + a.LIST_OBJECT_TYPE, | |
22 | + a.OBJECT_TYPE, | |
23 | + a.ID_FIELD, | |
24 | + a.LABEL_FIELD, | |
25 | + a.TYPE, | |
26 | + b.FIELD | |
22 | 27 | FROM LIST_SELECT_CONFIG a, LIST_VALUE_FIELD b |
23 | 28 | WHERE a.LIST_OBJECT_TYPE` + otype + ` |
24 | 29 | AND b.LIST_TYPE = a.LIST_OBJECT_TYPE |
... | ... | @@ -37,11 +42,11 @@ func GetSelectConfig(db *ora.Ses, otype string) ([]SelectConfig, error) { |
37 | 42 | for rset.Next() { |
38 | 43 | resp = append(resp, SelectConfig{ |
39 | 44 | ListObjType: rset.Row[0].(string), |
40 | - ObjType: rset.Row[1].(string), | |
41 | - IdField: rset.Row[2].(string), | |
42 | - LabelField: rset.Row[3].(string), | |
43 | - Type: rset.Row[4].(string), | |
44 | - ValueField: rset.Row[5].(string), | |
45 | + ObjType: rset.Row[1].(string), | |
46 | + IdField: rset.Row[2].(string), | |
47 | + LabelField: rset.Row[3].(string), | |
48 | + Type: rset.Row[4].(string), | |
49 | + ValueField: rset.Row[5].(string), | |
45 | 50 | }) |
46 | 51 | } |
47 | 52 | if rset.Err() != nil { | ... | ... |