Commit d2ddf82ef16c3ab5764f1b8136f1f387b117277b

Authored by Marko Tikvić
1 parent 6b16c88f25
Exists in master and in 1 other branch v2

started on new rbac

  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 +}
... ...
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 +}
... ...
... ... @@ -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
... ...
... ... @@ -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 {
... ...