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

mendersoftware / mender / 974575668

21 Aug 2023 12:04PM UTC coverage: 78.829% (-0.05%) from 78.877%
974575668

push

gitlab-ci

kacf
chore: Implement pushing of logs to the server.

Ticket: MEN-6581

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

18 of 18 new or added lines in 2 files covered. (100.0%)

5492 of 6967 relevant lines covered (78.83%)

238.75 hits per line

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

69.34
/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
namespace optional = mender::common::optional;
51

52

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

55
const AuthClientErrorCategoryClass AuthClientErrorCategory;
56

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

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

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

84
namespace http {
85

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

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

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

123
        if (tenant_token.size() > 0) {
13✔
124
                request_body_map.insert({"tenant_token", tenant_token});
1✔
125
        }
126

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

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

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

147
        auto whole_url = mender::http::JoinUrl(server_url, request_uri);
26✔
148

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

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

162
        auto received_body = make_shared<vector<uint8_t>>();
13✔
163

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

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

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

190
                        string response_body = common::StringFromByteVector(*received_body);
26✔
191

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

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

234
void Authenticator::ExpireToken() {
13✔
235
        unique_lock<mutex> lock {auth_lock_};
26✔
236
        token_ = optional::nullopt;
13✔
237
}
13✔
238

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

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

265
        return FetchJWTToken(
266
                client_,
11✔
267
                server_url_,
11✔
268
                private_key_path_,
11✔
269
                device_identity_script_path_,
11✔
270
                [this, action](APIResponse resp) {
49✔
271
                        if (resp) {
11✔
272
                                unique_lock<mutex> lock {auth_lock_};
9✔
273
                                token_ = resp.value();
9✔
274
                                auth_in_progress_ = false;
9✔
275
                        }
276
                        action(resp);
11✔
277
                        RunPendingActions(resp);
11✔
278
                },
11✔
279
                tenant_token_);
11✔
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