Skip to content

Commit 611afb5

Browse files
authored
Merge pull request #101 from ARGOeu/devel
Merge Devel into master to release version 0.1.3
2 parents ccd9ef4 + 47fad41 commit 611afb5

34 files changed

+1014
-305
lines changed

README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,19 @@ Before you start, you need to issue a valid certificate.
7171
"certificate_key":"/path/to/key/localhost.key",
7272
"service_token": "some-token",
7373
"supported_auth_types": ["x509"],
74-
"supported_auth_methods": ["api-key"],
75-
"supported_service_types": ["ams"],
74+
"supported_auth_methods": ["api-key", "headers"],
75+
"supported_service_types": ["ams", "web-api"],
7676
"verify_ssl": true,
7777
"trust_unknown_cas": false,
7878
"verify_certificate": true,
79-
"service_types_paths": {"ams": "/v1/users:byUUID/{{identifier}}?key={{access_key}}"},
80-
"service_types_retrieval_fields": {"ams": "token"}
79+
"service_types_paths": {
80+
"ams": "/v1/users:byUUID/{{identifier}}?key={{access_key}}",
81+
"web-api": "/api/v2/users:byID/{{identifier}}?export=flat"
82+
},
83+
"service_types_retrieval_fields": {
84+
"ams": "token",
85+
"web-api": "api_key"
86+
}
8187
}
8288
```
8389

@@ -116,6 +122,8 @@ but a reverse dns look up returns another hostname for the client from where the
116122

117123
## Feature Milestones
118124

119-
- Add support for authenticating with external services through x-api-key header.
120-
- Add default configuration for interacting easier with the [argo-web-api](https://github.com/ARGOeu/argo-web-api).
125+
- ~~Add support for authenticating with external services through x-api-key header.~~
126+
127+
- ~~Add default configuration for interacting easier with the [argo-web-api](https://github.com/ARGOeu/argo-web-api).~~
128+
121129
- Add support for using OIDC tokens as an alternative authentication mechanism.

argo-api-authn.spec

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
Name: argo-api-authn
55
Summary: ARGO Authentication API. Map X509, OICD to token.
6-
Version: 0.1.2
6+
Version: 0.1.3
77
Release: 1%{?dist}
88
License: ASL 2.0
99
Buildroot: %{_tmppath}/%{name}-buildroot
@@ -57,6 +57,12 @@ go clean
5757
%attr(0644,root,root) /usr/lib/systemd/system/argo-api-authn.service
5858

5959
%changelog
60+
* Thu Jun 13 2019 Agelos Tsalapatis <agelos.tsal@gmail.com> - 0.1.3-1%{?dist}
61+
- ARGO-1773 Update authn scripts to filter service endpoints before creating the respective user
62+
- ARGO-1615 update authn scripts to get site-mail from gocdb
63+
- ARGO-1738 Add support for interacting with the argo-web-api
64+
- ARGO-1737 Add support for headers auth method
65+
- ARGO-1740 Change binding structure to be more generic
6066
* Thu Mar 7 2019 Agelos Tsalapatis <agelos.tsal@gmail.com> - 0.1.2-1%{?dist}
6167
- ARGO-1659 Authn should not start if there is no database connection established
6268
- Utility script that creates users and topics per site

auth/certificate_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,15 @@ SoPmZKiBeb+2OQ2n7+FI8ftkqxWw6zjh651brAoy/0zqLTRPh+c=
8888
err1 := CertHasExpired(crt)
8989

9090
// expired case
91-
crt.NotAfter = time.Now().AddDate(0,0,-1)
91+
crt.NotAfter = time.Now().AddDate(0, 0, -1)
9292
err2 := CertHasExpired(crt)
9393

9494
// not active yet
9595
crt = ParseCert(commonCert)
9696
// move the not before date a day to the future so the check fails because we haven't reached that date yet
9797
crt.NotBefore = time.Now().Add(time.Hour * 24)
98+
// also move the not after date so we can skip the expiration case and check the not before case
99+
crt.NotAfter = time.Now().Add(time.Hour * 24)
98100
err3 := CertHasExpired(crt)
99101

100102
suite.Nil(err1)

authmethods/api_key_auth_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,6 @@ func (suite *ApiKeyAuthMethodTestSuite) TestUpdate() {
9292

9393
}
9494

95-
func TestApiKeyAuthMethod_Fill(t *testing.T) {
95+
func TestApiKeyAuthMethodSuite(t *testing.T) {
9696
suite.Run(t, new(ApiKeyAuthMethodTestSuite))
9797
}

authmethods/authmethods.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ type AuthMethodInit func() AuthMethod
1616

1717
var AuthMethodsTypes = map[string]AuthMethodInit{
1818
"api-key": NewApiKeyAuthMethod,
19+
"headers": NewHeadersAuthMethod,
1920
}
2021

2122
// A function type that refers to all the query functions for all the respective tuh method types
2223
type QueryAuthMethodFinder func(serviceUUID string, host string, store stores.Store) ([]stores.QAuthMethod, error)
2324

2425
var QueryAuthMethodFinders = map[string]QueryAuthMethodFinder{
2526
"api-key": ApiKeyAuthFinder,
27+
"headers": HeadersAuthFinder,
2628
}
2729

2830
type AuthMethod interface {

authmethods/authmethods_test.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,12 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodFIndAll() {
135135
amb1 := BasicAuthMethod{ServiceUUID: "uuid1", Host: "host1", Port: 9000, Type: "api-key", UUID: "am_uuid_1", CreatedOn: ""}
136136
am1 := &ApiKeyAuthMethod{AccessKey: "access_key"}
137137
am1.BasicAuthMethod = amb1
138-
expAmList.AuthMethods = append(expAmList.AuthMethods, am1)
138+
139+
amb2 := BasicAuthMethod{ServiceUUID: "uuid2", Host: "host3", Port: 9000, Type: "headers", UUID: "am_uuid_2", CreatedOn: ""}
140+
am2 := &HeadersAuthMethod{Headers: map[string]string{"x-api-key": "key-1", "Accept": "application/json"}}
141+
am2.BasicAuthMethod = amb2
142+
143+
expAmList.AuthMethods = append(expAmList.AuthMethods, am1, am2)
139144

140145
aMList, err1 := AuthMethodFindAll(mockstore)
141146

@@ -162,7 +167,7 @@ func (suite *AuthMethodsTestSuite) TestAuthMethodDelete() {
162167

163168
err1 := AuthMethodDelete(am1, mockstore)
164169

165-
suite.Equal(0, len(mockstore.AuthMethods))
170+
suite.Equal(1, len(mockstore.AuthMethods))
166171

167172
suite.Nil(err1)
168173
}

authmethods/headers_auth.go

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
package authmethods
2+
3+
import (
4+
"bytes"
5+
"crypto/tls"
6+
"encoding/json"
7+
"fmt"
8+
"github.com/ARGOeu/argo-api-authn/bindings"
9+
"github.com/ARGOeu/argo-api-authn/config"
10+
"github.com/ARGOeu/argo-api-authn/servicetypes"
11+
"github.com/ARGOeu/argo-api-authn/stores"
12+
"github.com/ARGOeu/argo-api-authn/utils"
13+
LOGGER "github.com/sirupsen/logrus"
14+
"io"
15+
"net/http"
16+
"strconv"
17+
"strings"
18+
"time"
19+
)
20+
21+
type HeadersAuthMethod struct {
22+
BasicAuthMethod
23+
Headers map[string]string `json:"headers" required:"true"`
24+
}
25+
26+
// TempHeadersAuthMethod represents the fields that are allowed to be modified
27+
type TempHeadersAuthMethod struct {
28+
TempBasicAuthMethod
29+
Headers map[string]string `json:"headers" required:"true"`
30+
}
31+
32+
func NewHeadersAuthMethod() AuthMethod {
33+
return new(HeadersAuthMethod)
34+
}
35+
36+
func (m *HeadersAuthMethod) Validate(store stores.Store) error {
37+
38+
var err error
39+
40+
// check if the embedded struct is valid
41+
if err = m.BasicAuthMethod.Validate(store); err != nil {
42+
return err
43+
}
44+
45+
// check if all required field have been provided
46+
if err = utils.ValidateRequired(*m); err != nil {
47+
err := utils.APIErrEmptyRequiredField("auth method", err.Error())
48+
return err
49+
}
50+
51+
// check that the headers map is not empty
52+
if len(m.Headers) == 0 {
53+
err := utils.APIErrEmptyRequiredField("auth method", utils.GenericEmptyRequiredField("headers").Error())
54+
return err
55+
}
56+
57+
return err
58+
}
59+
60+
func (m *HeadersAuthMethod) Update(r io.ReadCloser) (AuthMethod, error) {
61+
62+
var err error
63+
var authMBytes []byte
64+
var tempAM TempHeadersAuthMethod
65+
66+
var updatedAM = NewHeadersAuthMethod()
67+
68+
// first fill the temp auth method with the already existing data
69+
// convert the existing auth method to bytes
70+
if authMBytes, err = json.Marshal(*m); err != nil {
71+
err := utils.APIGenericInternalError(err.Error())
72+
return updatedAM, err
73+
}
74+
75+
// then load the bytes into the temp auth method
76+
if err = json.Unmarshal(authMBytes, &tempAM); err != nil {
77+
err := utils.APIGenericInternalError(err.Error())
78+
return updatedAM, err
79+
}
80+
81+
// check the validity of the JSON and fill the temp auth method object with the updated data
82+
if err = json.NewDecoder(r).Decode(&tempAM); err != nil {
83+
err := utils.APIErrBadRequest(err.Error())
84+
return updatedAM, err
85+
}
86+
87+
// close the reader
88+
if err = r.Close(); err != nil {
89+
err := utils.APIGenericInternalError(err.Error())
90+
return updatedAM, err
91+
}
92+
93+
// fill the updated auth method with the already existing data
94+
if err := utils.CopyFields(*m, updatedAM); err != nil {
95+
err = utils.APIGenericInternalError(err.Error())
96+
return updatedAM, err
97+
}
98+
99+
// transfer the updated temporary data to the updated auth method object
100+
// in order to override the outdated fields
101+
// convert to bytes
102+
if authMBytes, err = json.Marshal(tempAM); err != nil {
103+
err := utils.APIGenericInternalError(err.Error())
104+
return updatedAM, err
105+
}
106+
107+
// then load the bytes
108+
if err = json.Unmarshal(authMBytes, updatedAM); err != nil {
109+
err := utils.APIGenericInternalError(err.Error())
110+
return updatedAM, err
111+
}
112+
113+
return updatedAM, err
114+
}
115+
116+
func (m *HeadersAuthMethod) RetrieveAuthResource(binding bindings.Binding, serviceType servicetypes.ServiceType, cfg *config.Config) (map[string]interface{}, error) {
117+
118+
var externalResp map[string]interface{}
119+
var err error
120+
var ok bool
121+
var resp *http.Response
122+
var authResource interface{}
123+
var retrievalField string
124+
var path string
125+
126+
if retrievalField, ok = cfg.ServiceTypesRetrievalFields[serviceType.Type]; !ok {
127+
err = utils.APIGenericInternalError("Backend error")
128+
LOGGER.Errorf("The retrieval field for type: %v was not found in the config retrieval fields: %v", serviceType.Type, cfg.ServiceTypesRetrievalFields)
129+
return externalResp, err
130+
}
131+
132+
if path, ok = cfg.ServiceTypesPaths[serviceType.Type]; !ok {
133+
err = utils.APIGenericInternalError("Backend error")
134+
LOGGER.Errorf("The path for type: %v was not found in the config retrieval fields: %v", serviceType.Type, cfg.ServiceTypesPaths)
135+
return externalResp, err
136+
}
137+
138+
// build the path that identifies the resource we are going to request
139+
resourcePath := fmt.Sprintf("https://%v:%v%v", m.Host, strconv.Itoa(m.Port), path)
140+
resourcePath = strings.Replace(resourcePath, "{{identifier}}", binding.UniqueKey, 1)
141+
142+
// build the client and execute the request
143+
transCfg := &http.Transport{
144+
TLSClientConfig: &tls.Config{InsecureSkipVerify: !cfg.VerifySSL},
145+
}
146+
147+
client := &http.Client{Transport: transCfg, Timeout: time.Duration(30 * time.Second)}
148+
149+
req, err := http.NewRequest(http.MethodGet, resourcePath, nil)
150+
if err != nil {
151+
err = utils.APIGenericInternalError(err.Error())
152+
}
153+
154+
// populate the request with the headers
155+
for k, v := range m.Headers {
156+
req.Header.Add(k, v)
157+
}
158+
159+
resp, err = client.Do(req)
160+
if err != nil {
161+
err = utils.APIGenericInternalError(err.Error())
162+
return externalResp, err
163+
}
164+
165+
// evaluate the response
166+
if resp.StatusCode >= 400 {
167+
// convert the entire response body into a string and include into a genericAPIError
168+
buf := bytes.Buffer{}
169+
buf.ReadFrom(resp.Body)
170+
err = utils.APIGenericInternalError(buf.String())
171+
return externalResp, err
172+
}
173+
174+
// get the response from the service type
175+
if err = json.NewDecoder(resp.Body).Decode(&externalResp); err != nil {
176+
err = utils.APIGenericInternalError(err.Error())
177+
return externalResp, err
178+
}
179+
180+
defer resp.Body.Close()
181+
182+
// check if the retrieval field that we need is present in the response
183+
if authResource, ok = externalResp[retrievalField]; !ok {
184+
err = utils.APIGenericInternalError(fmt.Sprintf("The specified retrieval field: `%v` was not found in the response body of the service type", retrievalField))
185+
return externalResp, err
186+
}
187+
188+
// if everything went ok, return the appropriate response field
189+
return map[string]interface{}{"token": authResource}, err
190+
191+
}
192+
193+
func HeadersAuthFinder(serviceUUID string, host string, store stores.Store) ([]stores.QAuthMethod, error) {
194+
195+
var err error
196+
var qAms []stores.QAuthMethod
197+
var qApiAms []stores.QHeadersAuthMethod
198+
199+
if qApiAms, err = store.QueryHeadersAuthMethods(serviceUUID, host); err != nil {
200+
return qAms, err
201+
}
202+
203+
for _, apim := range qApiAms {
204+
qAms = append(qAms, &apim)
205+
}
206+
207+
return qAms, err
208+
}

0 commit comments

Comments
 (0)