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

mendersoftware / mender / 1019451413

28 Sep 2023 10:09AM UTC coverage: 78.477%. Remained the same
1019451413

push

gitlab-ci

kacf
chore: Make our `optional` use compatible with C++17.

If the standard is pre-C++17, we use the `optional-lite` library, else
we use the one in `std`.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

17 of 17 new or added lines in 6 files covered. (100.0%)

5546 of 7067 relevant lines covered (78.48%)

11103.41 hits per line

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

73.5
/api/auth/auth.cpp
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

15
#include <api/auth.hpp>
16

17
#include <mutex>
18
#include <string>
19
#include <vector>
20

21
#include <common/common.hpp>
22
#include <common/crypto.hpp>
23
#include <common/json.hpp>
24
#include <common/error.hpp>
25
#include <common/log.hpp>
26
#include <common/path.hpp>
27
#include <common/expected.hpp>
28
#include <common/identity_parser.hpp>
29
#include <common/optional.hpp>
30

31
namespace mender {
32
namespace api {
33
namespace auth {
34

35

36
using namespace std;
37
namespace error = mender::common::error;
38
namespace common = mender::common;
39
namespace conf = mender::common::conf;
40

41

42
namespace identity_parser = mender::common::identity_parser;
43
namespace key_value_parser = mender::common::key_value_parser;
44
namespace path = mender::common::path;
45
namespace mlog = mender::common::log;
46
namespace expected = mender::common::expected;
47
namespace io = mender::common::io;
48
namespace json = mender::common::json;
49
namespace crypto = mender::common::crypto;
50

51

52
const string request_uri = "/api/devices/v1/authentication/auth_requests";
53

54
const AuthClientErrorCategoryClass AuthClientErrorCategory;
55

56
const char *AuthClientErrorCategoryClass::name() const noexcept {
×
57
        return "AuthClientErrorCategory";
×
58
}
59

60
string AuthClientErrorCategoryClass::message(int code) const {
×
61
        switch (code) {
×
62
        case NoError:
63
                return "Success";
×
64
        case SetupError:
65
                return "Error during setup";
×
66
        case RequestError:
67
                return "HTTP client request error";
×
68
        case ResponseError:
69
                return "HTTP client response error";
×
70
        case APIError:
71
                return "API error";
×
72
        case UnauthorizedError:
73
                return "Unauthorized error";
×
74
        default:
75
                return "Unknown";
×
76
        }
77
}
78

79
error::Error MakeError(AuthClientErrorCode code, const string &msg) {
×
80
        return error::Error(error_condition(code, AuthClientErrorCategory), msg);
2✔
81
}
82

83
namespace http {
84

85
error::Error MakeHTTPResponseError(
×
86
        const AuthClientErrorCode code,
87
        const mender::http::ResponsePtr resp,
88
        const string &response_body,
89
        const string &msg) {
90
        return error::Error(
91
                error_condition(code, AuthClientErrorCategory),
×
92
                "Authentication error(" + resp->GetStatusMessage() + "): " + msg + "(" + response_body
×
93
                        + ")");
×
94
}
95

96
error::Error FetchJWTToken(
14✔
97
        mender::http::Client &client,
98
        const string &server_url,
99
        const string &private_key_path,
100
        const string &device_identity_script_path,
101
        APIResponseHandler api_handler,
102
        const string &tenant_token) {
103
        key_value_parser::ExpectedKeyValuesMap expected_identity_data =
104
                identity_parser::GetIdentityData(device_identity_script_path);
14✔
105
        if (!expected_identity_data) {
14✔
106
                return expected_identity_data.error();
107
        }
108
        expected::ExpectedString expected_identity_data_json =
109
                json::Dump(expected_identity_data.value());
42✔
110
        if (!expected_identity_data_json) {
14✔
111
                mlog::Error("Failed to dump the identity data to JSON");
×
112
                return expected_identity_data_json.error();
113
        }
114
        auto identity_data_json = expected_identity_data_json.value();
14✔
115
        mlog::Debug("Got identity data: " + identity_data_json);
14✔
116

117
        // Create the request body
118
        unordered_map<string, string> request_body_map {
119
                {"id_data", identity_data_json},
120
        };
56✔
121

122
        if (tenant_token.size() > 0) {
14✔
123
                request_body_map.insert({"tenant_token", tenant_token});
2✔
124
        }
125

126
        auto expected_public_key = crypto::ExtractPublicKey(private_key_path);
14✔
127
        if (!expected_public_key) {
14✔
128
                return expected_public_key.error();
129
        }
130
        request_body_map.insert({"pubkey", expected_public_key.value()});
26✔
131

132
        auto expected_request_body = json::Dump(request_body_map);
26✔
133
        if (!expected_request_body) {
13✔
134
                return expected_request_body.error();
135
        }
136
        auto request_body = expected_request_body.value();
13✔
137

138
        // Sign the body
139
        auto expected_signature =
140
                crypto::SignRawData(private_key_path, common::ByteVectorFromString(request_body));
26✔
141
        if (!expected_signature) {
13✔
142
                return expected_signature.error();
143
        }
144
        auto signature = expected_signature.value();
13✔
145

146
        auto whole_url = mender::http::JoinUrl(server_url, request_uri);
13✔
147

148
        auto req = make_shared<mender::http::OutgoingRequest>();
149
        req->SetMethod(mender::http::Method::POST);
13✔
150
        req->SetAddress(whole_url);
26✔
151
        req->SetHeader("Content-Type", "application/json");
39✔
152
        req->SetHeader("Content-Length", to_string(request_body.size()));
26✔
153
        req->SetHeader("Accept", "application/json");
39✔
154
        req->SetHeader("X-MEN-Signature", signature);
26✔
155
        req->SetHeader("Authorization", "API_KEY");
26✔
156

157
        req->SetBodyGenerator([request_body]() -> io::ExpectedReaderPtr {
52✔
158
                return make_shared<io::StringReader>(request_body);
13✔
159
        });
26✔
160

161
        auto received_body = make_shared<vector<uint8_t>>();
162

163
        return client.AsyncCall(
164
                req,
165
                [received_body, api_handler](mender::http::ExpectedIncomingResponsePtr exp_resp) {
13✔
166
                        if (!exp_resp) {
13✔
167
                                mlog::Error("Request failed: " + exp_resp.error().message);
×
168
                                api_handler(expected::unexpected(exp_resp.error()));
×
169
                                return;
×
170
                        }
171
                        auto resp = exp_resp.value();
13✔
172

173
                        auto body_writer = make_shared<io::ByteWriter>(received_body);
13✔
174
                        body_writer->SetUnlimited(true);
13✔
175
                        resp->SetBodyWriter(body_writer);
13✔
176

177
                        mlog::Debug("Received response header value:");
26✔
178
                        mlog::Debug("Status code:" + to_string(resp->GetStatusCode()));
26✔
179
                        mlog::Debug("Status message: " + resp->GetStatusMessage());
26✔
180
                },
181
                [received_body, api_handler](mender::http::ExpectedIncomingResponsePtr exp_resp) {
13✔
182
                        if (!exp_resp) {
13✔
183
                                mlog::Error("Request failed: " + exp_resp.error().message);
×
184
                                api_handler(expected::unexpected(exp_resp.error()));
×
185
                                return;
×
186
                        }
187
                        auto resp = exp_resp.value();
13✔
188

189
                        string response_body = common::StringFromByteVector(*received_body);
190

191
                        switch (resp->GetStatusCode()) {
13✔
192
                        case mender::http::StatusOK:
11✔
193
                                api_handler(response_body);
11✔
194
                                return;
11✔
195
                        case mender::http::StatusUnauthorized:
×
196
                                api_handler(expected::unexpected(MakeHTTPResponseError(
×
197
                                        UnauthorizedError,
198
                                        resp,
199
                                        response_body,
200
                                        "Failed to authorize with the server.")));
×
201
                                return;
×
202
                        case mender::http::StatusBadRequest:
×
203
                        case mender::http::StatusInternalServerError:
204
                                api_handler(expected::unexpected(MakeHTTPResponseError(
×
205
                                        APIError, resp, response_body, "Failed to authorize with the server.")));
×
206
                                return;
×
207
                        default:
208
                                mlog::Error("Unexpected error code " + resp->GetStatusMessage());
4✔
209
                                api_handler(expected::unexpected(MakeError(
4✔
210
                                        ResponseError, "Unexpected error code: " + resp->GetStatusMessage())));
6✔
211
                                return;
2✔
212
                        }
213
                });
78✔
214
}
215
} // namespace http
216

217
error::Error FetchJWTToken(
14✔
218
        mender::http::Client &client,
219
        const string &server_url,
220
        const string &private_key_path,
221
        const string &device_identity_script_path,
222
        APIResponseHandler api_handler,
223
        const string &tenant_token) {
224
        return http::FetchJWTToken(
225
                client,
226
                server_url,
227
                private_key_path,
228
                device_identity_script_path,
229
                api_handler,
230
                tenant_token);
28✔
231
}
232

233
void Authenticator::ExpireToken() {
25✔
234
        unique_lock<mutex> lock {auth_lock_};
25✔
235
        token_ = nullopt;
236
}
25✔
237

238
void Authenticator::RunPendingActions(ExpectedToken ex_token) {
11✔
239
        unique_lock<mutex> lock {auth_lock_};
11✔
240
        for (auto action : pending_actions_) {
13✔
241
                loop_.Post([action, ex_token]() { action(ex_token); });
12✔
242
        }
243
        pending_actions_.clear();
244
}
11✔
245

246
error::Error Authenticator::WithToken(AuthenticatedAction action) {
16✔
247
        unique_lock<mutex> lock {auth_lock_};
16✔
248
        if (token_) {
16✔
249
                string token = *token_;
250
                lock.unlock();
2✔
251
                action(ExpectedToken(token));
2✔
252
                return error::NoError;
253
        }
254
        // else => no token
255
        if (auth_in_progress_) {
14✔
256
                pending_actions_.push_back(action);
2✔
257
                lock.unlock();
2✔
258
                return error::NoError;
259
        }
260
        // else => should fetch the token, cache it and call all pending actions
261
        auth_in_progress_ = true;
12✔
262
        lock.unlock();
12✔
263

264
        return FetchJWTToken(
265
                client_,
12✔
266
                server_url_,
12✔
267
                private_key_path_,
12✔
268
                device_identity_script_path_,
12✔
269
                [this, action](APIResponse resp) {
168✔
270
                        unique_lock<mutex> lock {auth_lock_};
11✔
271
                        if (resp) {
11✔
272
                                token_ = resp.value();
9✔
273
                        }
274
                        auth_in_progress_ = false;
11✔
275
                        lock.unlock();
11✔
276
                        action(resp);
11✔
277
                        RunPendingActions(resp);
22✔
278
                },
11✔
279
                tenant_token_);
24✔
280
}
281

282
} // namespace auth
283
} // namespace api
284
} // namespace mender
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