• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / deviceauth / 1284672998

09 May 2024 01:19PM UTC coverage: 81.658% (-1.1%) from 82.796%
1284672998

Pull #715

gitlab-ci

alfrunes
test(acceptance/os): :broom: Remove unused fixtures

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #715: Acceptance test fixup

4808 of 5888 relevant lines covered (81.66%)

51.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.76
/client/tenant/client_tenantadm.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package tenant
15

16
import (
17
        "context"
18
        "encoding/json"
19
        "net/http"
20
        "strings"
21
        "time"
22

23
        "github.com/mendersoftware/go-lib-micro/log"
24
        "github.com/mendersoftware/go-lib-micro/rest_utils"
25
        "github.com/pkg/errors"
26

27
        "github.com/mendersoftware/deviceauth/utils"
28
)
29

30
const (
31
        TenantHealthURI = "/api/internal/v1/tenantadm/health"
32
        // devices endpoint
33
        TenantVerifyUri = "/api/internal/v1/tenantadm/tenants/verify"
34
        TenantGetUri    = "/api/internal/v1/tenantadm/tenants/#tid"
35
        TenantUsersURI  = "/api/internal/v1/tenantadm/tenants/users"
36
        // default request timeout, 10s?
37
        defaultReqTimeout = time.Duration(10) * time.Second
38
)
39

40
const (
41
        MsgErrTokenVerificationFailed = "tenant token verification failed"
42
)
43

44
func IsErrTokenVerificationFailed(e error) bool {
×
45
        return strings.HasPrefix(e.Error(), MsgErrTokenVerificationFailed)
×
46
}
×
47

48
func MakeErrTokenVerificationFailed(apiErr error) error {
2✔
49
        return errors.Wrap(apiErr, MsgErrTokenVerificationFailed)
2✔
50
}
2✔
51

52
// ClientConfig conveys client configuration
53
type Config struct {
54
        // Tenant administrator service address
55
        TenantAdmAddr string
56
        // Request timeout
57
        Timeout time.Duration
58
}
59

60
// ClientRunner is an interface of inventory client
61
//
62
//go:generate ../../utils/mockgen.sh
63
type ClientRunner interface {
64
        CheckHealth(ctx context.Context) error
65
        VerifyToken(ctx context.Context, token string) (*Tenant, error)
66
        GetTenant(ctx context.Context, tid string) (*Tenant, error)
67
        GetTenantUsers(ctx context.Context, tenantID string) ([]User, error)
68
}
69

70
// Client is an opaque implementation of tenant administrator client. Implements
71
// ClientRunner interface
72
type Client struct {
73
        conf Config
74
        http http.Client
75
}
76

77
// NewClient creates a client with given config.
78
func NewClient(c Config) *Client {
34✔
79
        if c.Timeout == 0 {
68✔
80
                c.Timeout = defaultReqTimeout
34✔
81
        }
34✔
82

83
        return &Client{
34✔
84
                conf: c,
34✔
85
                http: http.Client{
34✔
86
                        Timeout: c.Timeout,
34✔
87
                },
34✔
88
        }
34✔
89
}
90

91
func (c *Client) CheckHealth(ctx context.Context) error {
8✔
92
        var (
8✔
93
                apiErr rest_utils.ApiError
8✔
94
        )
8✔
95

8✔
96
        if ctx == nil {
10✔
97
                ctx = context.Background()
2✔
98
        }
2✔
99
        if _, ok := ctx.Deadline(); !ok {
12✔
100
                var cancel context.CancelFunc
4✔
101
                ctx, cancel = context.WithTimeout(ctx, c.conf.Timeout)
4✔
102
                defer cancel()
4✔
103
        }
4✔
104
        req, _ := http.NewRequestWithContext(
8✔
105
                ctx, "GET",
8✔
106
                utils.JoinURL(c.conf.TenantAdmAddr, TenantHealthURI), nil,
8✔
107
        )
8✔
108

8✔
109
        rsp, err := c.http.Do(req)
8✔
110
        if err != nil {
10✔
111
                return err
2✔
112
        }
2✔
113
        defer rsp.Body.Close()
6✔
114
        if rsp.StatusCode >= http.StatusOK && rsp.StatusCode < 300 {
8✔
115
                return nil
2✔
116
        }
2✔
117
        decoder := json.NewDecoder(rsp.Body)
4✔
118
        err = decoder.Decode(&apiErr)
4✔
119
        if err != nil {
6✔
120
                return errors.Errorf("health check HTTP error: %s", rsp.Status)
2✔
121
        }
2✔
122
        return &apiErr
2✔
123
}
124

125
// VerifyToken will execute a request to tenenatadm's endpoint for token
126
// verification. Returns nil if verification was successful.
127
func (tc *Client) VerifyToken(ctx context.Context, token string) (*Tenant, error) {
8✔
128

8✔
129
        l := log.FromContext(ctx)
8✔
130

8✔
131
        // TODO sanitize token
8✔
132

8✔
133
        url := utils.JoinURL(tc.conf.TenantAdmAddr, TenantVerifyUri)
8✔
134

8✔
135
        req, err := http.NewRequest(http.MethodPost, url, nil)
8✔
136
        if err != nil {
8✔
137
                return nil, errors.Wrap(err, "failed to create request to tenant administrator")
×
138
        }
×
139

140
        // tenant token is passed in Authorization header
141
        req.Header.Add("Authorization", "Bearer "+token)
8✔
142

8✔
143
        ctx, cancel := context.WithTimeout(ctx, tc.conf.Timeout)
8✔
144
        defer cancel()
8✔
145

8✔
146
        rsp, err := tc.http.Do(req.WithContext(ctx))
8✔
147
        if err != nil {
8✔
148
                l.Errorf("tenantadm request failed: %v", err)
×
149
                return nil, errors.Wrap(err, "request to verify token failed")
×
150
        }
×
151
        defer rsp.Body.Close()
8✔
152

8✔
153
        switch rsp.StatusCode {
8✔
154

155
        case http.StatusUnauthorized: // 401, verification result negative
2✔
156
                apiErr := rest_utils.ParseApiError(rsp.Body)
2✔
157
                if !rest_utils.IsApiError(apiErr) {
2✔
158
                        return nil, errors.Errorf("failed to parse tenantadm api error response")
×
159
                }
×
160

161
                return nil, MakeErrTokenVerificationFailed(apiErr)
2✔
162

163
        case http.StatusOK: // 200, token verified
2✔
164
                tenant := Tenant{}
2✔
165
                if err := json.NewDecoder(rsp.Body).Decode(&tenant); err != nil {
2✔
166
                        return nil, errors.Wrap(err, "error parsing tenant verification response")
×
167
                }
×
168
                return &tenant, nil
2✔
169
        default:
4✔
170
                return nil, errors.Errorf("token verification request returned unexpected status %v",
4✔
171
                        rsp.StatusCode)
4✔
172
        }
173
}
174

175
// GetTenant will retrieve a single tenant
176
// verification. Returns nil if verification was successful.
177
func (tc *Client) GetTenant(ctx context.Context, tid string) (*Tenant, error) {
6✔
178

6✔
179
        l := log.FromContext(ctx)
6✔
180

6✔
181
        repl := strings.NewReplacer("#tid", tid)
6✔
182
        uri := repl.Replace(TenantGetUri)
6✔
183

6✔
184
        req, err := http.NewRequest(http.MethodGet,
6✔
185
                utils.JoinURL(tc.conf.TenantAdmAddr, uri),
6✔
186
                nil)
6✔
187
        if err != nil {
6✔
188
                return nil, errors.Wrap(err, "failed to create request to tenantadm")
×
189
        }
×
190

191
        ctx, cancel := context.WithTimeout(ctx, tc.conf.Timeout)
6✔
192
        defer cancel()
6✔
193

6✔
194
        rsp, err := tc.http.Do(req.WithContext(ctx))
6✔
195
        if err != nil {
6✔
196
                l.Errorf("tenantadm request failed: %v", err)
×
197
                return nil, errors.Wrap(err, "request to get tenant failed")
×
198
        }
×
199
        defer rsp.Body.Close()
6✔
200

6✔
201
        switch rsp.StatusCode {
6✔
202
        case http.StatusNotFound:
2✔
203
                return nil, nil
2✔
204
        case http.StatusOK:
2✔
205
                tenant := Tenant{}
2✔
206
                if err := json.NewDecoder(rsp.Body).Decode(&tenant); err != nil {
2✔
207
                        return nil, errors.Wrap(err, "error parsing tenant")
×
208
                }
×
209
                return &tenant, nil
2✔
210
        default:
2✔
211
                return nil, errors.Errorf("getting tenant resulted in unexpected code: %v",
2✔
212
                        rsp.StatusCode)
2✔
213
        }
214
}
215

216
type User struct {
217
        ID       string `json:"id"`
218
        Email    string `json:"name"`
219
        TenantID string `json:"tenant_id"`
220
}
221

222
func (tc *Client) GetTenantUsers(ctx context.Context, tenantID string) ([]User, error) {
16✔
223
        var ret []User
16✔
224
        if tenantID == "" {
18✔
225
                return nil, errors.New("tenantadm: [internal] bad argument " +
2✔
226
                        "tenantID: cannot be empty")
2✔
227
        }
2✔
228

229
        req, err := http.NewRequestWithContext(
14✔
230
                ctx,
14✔
231
                "GET",
14✔
232
                utils.JoinURL(tc.conf.TenantAdmAddr, TenantUsersURI),
14✔
233
                nil,
14✔
234
        )
14✔
235
        if err != nil {
18✔
236
                return nil, errors.Wrap(err, "tenantadm: failed to prepare request")
4✔
237
        }
4✔
238
        q := req.URL.Query()
10✔
239
        q.Add("tenant_id", tenantID)
10✔
240
        req.URL.RawQuery = q.Encode()
10✔
241

10✔
242
        rsp, err := tc.http.Do(req)
10✔
243
        if err != nil {
12✔
244
                return nil, errors.Wrap(err, "tenantadm: error sending user request")
2✔
245
        }
2✔
246
        defer rsp.Body.Close()
8✔
247
        if rsp.StatusCode >= 400 {
12✔
248
                var APIErr = new(rest_utils.ApiError)
4✔
249
                jsDecoder := json.NewDecoder(rsp.Body)
4✔
250
                err = jsDecoder.Decode(APIErr)
4✔
251
                if err != nil {
6✔
252
                        return nil, errors.Errorf(
2✔
253
                                "tenantadm: unexpected HTTP status: %s",
2✔
254
                                rsp.Status,
2✔
255
                        )
2✔
256
                }
2✔
257
                return nil, errors.Wrapf(APIErr,
2✔
258
                        "tenantadm: HTTP error (%s) on user request",
2✔
259
                        rsp.Status,
2✔
260
                )
2✔
261
        }
262
        jsDecoder := json.NewDecoder(rsp.Body)
4✔
263
        err = jsDecoder.Decode(&ret)
4✔
264
        if err != nil {
6✔
265
                return nil, errors.Wrap(err,
2✔
266
                        "tenantadm: error decoding response payload",
2✔
267
                )
2✔
268
        }
2✔
269
        return ret, nil
2✔
270
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc