Skip to content

Commit aa93f04

Browse files
committed
registry and external service requests
1 parent 7dc1b21 commit aa93f04

File tree

5 files changed

+261
-1
lines changed

5 files changed

+261
-1
lines changed

pkg/externalsvc/auth.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package externalsvc
2+
3+
import (
4+
"errors"
5+
"os"
6+
"time"
7+
8+
jwt "github.com/dgrijalva/jwt-go"
9+
)
10+
11+
const (
12+
ExpirationKey = "exp"
13+
UserIDKey = "uid"
14+
IssuerKey = "iss"
15+
JWTIDKey = "jti"
16+
AudienceKey = "aud"
17+
SessionDuration = 8760 * time.Hour // 1 year
18+
)
19+
20+
func parser(token *jwt.Token) (interface{}, error) {
21+
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
22+
return nil, errors.New("Unexpected signing method")
23+
}
24+
return getJWTSecret(), nil
25+
}
26+
27+
func GenerateSessionJWT(userID uint64) (string, error) {
28+
expiration := time.Now().Add(SessionDuration).Unix()
29+
claims := jwt.MapClaims{
30+
IssuerKey: os.Getenv("FRONTEND_ADDRESS"),
31+
ExpirationKey: expiration,
32+
UserIDKey: userID,
33+
}
34+
return generateJWT(claims, getJWTSecret())
35+
}
36+
37+
func GenerateAPIKeyJWT(userID uint64, jwtID string) (string, error) {
38+
claims := jwt.MapClaims{
39+
IssuerKey: os.Getenv("FRONTEND_ADDRESS"),
40+
JWTIDKey: jwtID,
41+
UserIDKey: userID,
42+
}
43+
return generateJWT(claims, getJWTSecret())
44+
}
45+
46+
func GenerateWebhookJWT(url string, secret []byte) (string, error) {
47+
claims := jwt.MapClaims{
48+
IssuerKey: os.Getenv("FRONTEND_ADDRESS"),
49+
AudienceKey: url,
50+
}
51+
return generateJWT(claims, secret)
52+
}
53+
54+
func generateJWT(claims jwt.MapClaims, secret []byte) (string, error) {
55+
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
56+
return token.SignedString(secret)
57+
}
58+
59+
func ParseAPIKeyJWT(tokenString string) (uint64, string, error) {
60+
claims, err := parseJWT(tokenString)
61+
if err != nil {
62+
return 0, "", err
63+
}
64+
65+
issuer := claims[IssuerKey].(string)
66+
if issuer != os.Getenv("FRONTEND_ADDRESS") {
67+
return 0, "", errors.New("Incorrect issuer")
68+
}
69+
70+
jwtID := claims[JWTIDKey].(string)
71+
userID := uint64(claims[UserIDKey].(float64))
72+
return userID, jwtID, nil
73+
}
74+
75+
func ParseSessionJWT(tokenString string) (uint64, error) {
76+
claims, err := parseJWT(tokenString)
77+
if err != nil {
78+
return 0, err
79+
}
80+
81+
issuer := claims[IssuerKey].(string)
82+
if issuer != os.Getenv("FRONTEND_ADDRESS") {
83+
return 0, errors.New("Incorrect issuer")
84+
}
85+
86+
expiration := int64(claims[ExpirationKey].(float64))
87+
if expiration < time.Now().Unix() {
88+
return 0, errors.New("Authorization token expired")
89+
}
90+
91+
userID := uint64(claims[UserIDKey].(float64))
92+
return userID, nil
93+
}
94+
95+
func parseJWT(tokenString string) (jwt.MapClaims, error) {
96+
token, err := jwt.Parse(tokenString, parser)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
102+
return claims, nil
103+
}
104+
return nil, errors.New("Unable to parse JWT")
105+
}
106+
107+
func getJWTSecret() []byte {
108+
return []byte(os.Getenv("JWT_SECRET"))
109+
}

pkg/externalsvc/errors.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package externalsvc
2+
3+
type AssignmentRejectedByJobOwner struct{}
4+
5+
func (err AssignmentRejectedByJobOwner) Error() string {
6+
return "Job owner has rejected this assignment through external service"
7+
}

pkg/externalsvc/external.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package externalsvc
2+
3+
import (
4+
"encoding/json"
5+
"io/ioutil"
6+
"net/http"
7+
8+
"github.com/gemsorg/assignment/pkg/assignment"
9+
"github.com/gemsorg/assignment/pkg/registrysvc"
10+
)
11+
12+
type ValidationResponse struct {
13+
Valid bool `json:"valid"`
14+
Reason string `json:"reason"`
15+
}
16+
17+
func Validate(reg registrysvc.Registration, a assignment.NewAssignment) (bool, error) {
18+
esr := ValidationResponse{}
19+
url := reg.Services[registrysvc.AssignmentValidator].URL
20+
res, err := serviceRequest("POST", url, reg.APIKeyID, reg.RequesterID)
21+
if err != nil {
22+
return false, err
23+
}
24+
25+
err = json.Unmarshal(res, &esr)
26+
if err != nil {
27+
return false, err
28+
}
29+
30+
if !esr.Valid {
31+
return false, AssignmentRejectedByJobOwner{}
32+
}
33+
34+
return true, nil
35+
}
36+
37+
func serviceRequest(action, url, key string, userID uint64) ([]byte, error) {
38+
client := &http.Client{}
39+
40+
req, err := http.NewRequest(action, url, nil)
41+
if err != nil {
42+
return nil, err
43+
}
44+
apiKey, err := GenerateAPIKeyJWT(userID, key)
45+
if err != nil {
46+
return nil, err
47+
}
48+
req.Header.Add("Authorization", apiKey)
49+
r, err := client.Do(req)
50+
if err != nil {
51+
return nil, err
52+
}
53+
54+
body, err := ioutil.ReadAll(r.Body)
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
return body, nil
60+
}

pkg/registrysvc/request.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package registrysvc
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"net/http"
8+
"os"
9+
10+
"github.com/gemsorg/assignment/pkg/apierror"
11+
)
12+
13+
const (
14+
AssignmentValidator = "AssignmentValidator"
15+
AssignmentCreator = "AssignmentCreator"
16+
)
17+
18+
type Registration struct {
19+
ID uint64 `json:"id"`
20+
JobID uint64 `json:"job_id"`
21+
APIKeyID string `json:"api_key_id"`
22+
Services Services `json:"services"`
23+
RequesterID uint64 `json:"requester_id"`
24+
}
25+
26+
type Services map[string]*Service
27+
28+
type Service struct {
29+
URL string `json:"url"`
30+
}
31+
32+
func GetRegistration(authToken string, jobID uint64) (*Registration, error) {
33+
r := &Registration{}
34+
35+
url := fmt.Sprintf("registrations/%d", jobID)
36+
res, err := serviceRequest("GET", url, authToken)
37+
json.Unmarshal(res, r)
38+
39+
if err != nil {
40+
return nil, err
41+
}
42+
fmt.Printf("R %+v\n", r)
43+
return r, nil
44+
}
45+
46+
func serviceRequest(action, route, authToken string) ([]byte, error) {
47+
client := &http.Client{}
48+
serviceURL := fmt.Sprintf("%s/%s", os.Getenv("REGISTRY_SVC_URL"), route)
49+
50+
req, err := http.NewRequest(action, serviceURL, nil)
51+
if err != nil {
52+
return nil, errorResponse(err)
53+
}
54+
req.Header.Add("Authorization", "Bearer "+authToken)
55+
r, err := client.Do(req)
56+
if err != nil {
57+
return nil, errorResponse(err)
58+
}
59+
60+
body, err := ioutil.ReadAll(r.Body)
61+
if err != nil {
62+
return nil, errorResponse(err)
63+
}
64+
65+
return body, nil
66+
}
67+
68+
func errorResponse(err error) *apierror.APIError {
69+
return apierror.New(500, err.Error(), err)
70+
}

pkg/service/service.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"github.com/gemsorg/assignment/pkg/authentication"
66
"github.com/gemsorg/assignment/pkg/authorization"
77
"github.com/gemsorg/assignment/pkg/datastore"
8+
"github.com/gemsorg/assignment/pkg/externalsvc"
9+
"github.com/gemsorg/assignment/pkg/registrysvc"
810
"github.com/gemsorg/assignment/pkg/tasksvc"
911
)
1012

@@ -21,6 +23,7 @@ type AssignmentService interface {
2123
CreateSettings(assignment.Settings) (*assignment.Settings, error)
2224
GetStore() datastore.Storage
2325
ValidateAssignment(a assignment.NewAssignment, set *assignment.Settings) (bool, error)
26+
GetRegistration(jobID uint64) (*registrysvc.Registration, error)
2427
}
2528

2629
type service struct {
@@ -128,11 +131,22 @@ func (s *service) ValidateAssignment(a assignment.NewAssignment, set *assignment
128131
assigned, err := s.store.WorkerAlreadyAssigned(a.JobID, a.WorkerID)
129132
a.WorkerAlreadyAssigned = assigned
130133

134+
// Check if there's an external service registered for this task
135+
r, err := s.GetRegistration(a.JobID)
136+
if r != nil && r.Services[registrysvc.AssignmentValidator] != nil {
137+
return externalsvc.Validate(*r, a)
138+
} else {
139+
allowed, err = a.IsAllowed(set)
140+
}
141+
131142
// Check if the assignment is allowed
132-
allowed, err = a.IsAllowed(set)
133143
if !allowed {
134144
return false, err
135145
}
136146

137147
return allowed, nil
138148
}
149+
150+
func (s *service) GetRegistration(jobID uint64) (*registrysvc.Registration, error) {
151+
return registrysvc.GetRegistration(s.authorizor.GetAuthToken(), jobID)
152+
}

0 commit comments

Comments
 (0)