Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated mode resource and tests. #55

Merged
merged 2 commits into from
Mar 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion captived/integration-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ endforeach()

# Copy the test config directory
add_custom_command(TARGET integration-tests POST_BUILD
COMMAND cmake -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/config ${CURRENT_TEST_BINARY_DIR}/config
# using cp because copy_directory does not preserve symbolic links
COMMAND cp -P -R ${CMAKE_CURRENT_SOURCE_DIR}/config ${CURRENT_TEST_BINARY_DIR}/config
)

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
junk passthrough file
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
junk secure-host file
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
junk secure-lan file --- this isn't implemented yet.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
junk passthrough file
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
junk secure-host file
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
junk secure-lan file --- this isn't implemented yet.
23 changes: 13 additions & 10 deletions captived/integration-tests/mode_Test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ def setUp(self):
super(mode_Test, self).setUp()

def tearDown(self):
# put back to secure_host
resp = requests.put(URL, headers=HEADERS, json='secure_host')
# put back to secure-host
resp = requests.put(URL, headers=HEADERS, json='secure-host')
super(mode_Test, self).tearDown()

# This should get back the defult mode - which is secure_host
# This should get back the defult mode - which is secure-host
def test_01_get_mode(self):
resp = requests.get(URL)
self.assertEqual(resp.json(), "secure_host")
self.assertEqual(resp.json(), "secure-host")

def test_02_put_invalid_mode(self):
resp = requests.put(URL, headers=HEADERS, json='invalid-mode')
Expand All @@ -62,19 +62,22 @@ def test_04_put_invalid2(self):
self.assertEqual(resp.json(), orig_mode)

def test_05_put_secure_lan(self):
resp = requests.put(URL, headers=HEADERS, json='secure_lan')
resp = requests.put(URL, headers=HEADERS, json='secure-lan')
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.json(), 'secure_lan')
self.assertEqual(resp.json(), 'secure-lan')

def test_06_put_secure_host(self):
resp = requests.put(URL, headers=HEADERS, json='secure_host')
resp = requests.put(URL, headers=HEADERS, json='secure-host')
self.assertEqual(resp.status_code, 200)
self.assertEqual(resp.json(), 'secure_host')
self.assertEqual(resp.json(), 'secure-host')

def test_get_mode(self):
router_mode = os.path.join(DATA_PATH, 'data', 'default_target')
link_path = os.path.join(DATA_PATH, 'data', 'systemd', 'system', 'default.target')
real_path = os.path.realpath(link_path)
filename = os.path.basename(real_path)
router_mode = os.path.splitext(filename)[0]
resp = requests.get(URL)
self.assertMatchesFirstLineOfFile(router_mode, resp.json())
self.assertEqual(router_mode, resp.json())


if __name__ == '__main__':
Expand Down
4 changes: 2 additions & 2 deletions captived/integration-tests/root_level_status_Test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ def test_get_root(self):
('/rom/mac_address/1', 'mac_address'),
('/etc/mender/artifact_info', 'firmware_version'),
('/data/enftun/enf1/address', 'control_address'),
('/data/enftun/enf0/address', 'data_address'),
('/data/default_target', 'mode')
('/data/enftun/enf0/address', 'data_address')
]
for pair in check_these:
self.assertMatchesFirstLineOfFile(DATA_PATH + pair[0], jresp[pair[1]])
self.assertEqual('secure-host', jresp['mode'])



Expand Down
10 changes: 7 additions & 3 deletions captived/src/defines.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ const std::string FILE_WIFI_MAC_ADDRESS = "/rom/mac_address/1";
const std::string FILE_ENF_CONTROL_ADDRESS = "/data/enftun/enf1/address";
const std::string FILE_ENF_DATA_ADDRESS = "/data/enftun/enf0/address";
const std::string FILE_FIRMWARE_VERSION = "/etc/mender/artifact_info";
const std::string FILE_ROUTER_MODE = "/data/default_target";
const std::string FILE_WIFI_CONFIG_PASSTHROUGH =
"/data/connman/passthrough/wifi.config";
const std::string FILE_WIFI_CONFIG_SECURE_HOST =
"/data/connman/secure-host/wifi.config";
const std::string FILE_REBOOT_EXE = "/sbin/reboot";
const std::string LINK_MODE = "/data/systemd/system/default.target";
const std::string PATH_MODE_TARGET = "/lib/systemd/system";

const std::string URI_SERIAL_NUMBER = "/serial_number";
const std::string URI_WIFI_MAC_ADDRESS = "/mac_address";
Expand All @@ -35,9 +36,12 @@ const std::string URI_REBOOT = "/reboot";

// valid values for /mode URI
const std::string MODE_PASSTHROUGH = "passthrough";
const std::string MODE_SECURE_HOST = "secure_host";
const std::string MODE_SECURE_LAN = "secure_lan";
const std::string MODE_SECURE_HOST = "secure-host";
const std::string MODE_SECURE_LAN = "secure-lan";

extern const char* CONTENT_TYPE_JSON;

// define this so we don't use OS-specific values
const int ROUTER_CARD_PATH_MAX = 1024;

} // namespace captived
2 changes: 1 addition & 1 deletion captived/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ int main(int argc, char* argv[]) {
rest::line_resource firmware_version(URI_FIRMWARE_VERSION, sys,
FILE_FIRMWARE_VERSION, false);

rest::mode router_mode(URI_ROUTER_MODE, sys, FILE_ROUTER_MODE);
rest::mode router_mode(URI_ROUTER_MODE, sys, PATH_MODE_TARGET, LINK_MODE);

rest::wifi_config wifi_config_passthrough(URI_WIFI_CONFIG_PASSTHROUGH, sys,
FILE_WIFI_CONFIG_PASSTHROUGH);
Expand Down
76 changes: 69 additions & 7 deletions captived/src/rest/mode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,39 @@ bool is_valid_mode(std::string mode) {

namespace rest {

mode::mode(std::string path, system& system, std::string filename)
: line_resource(path, system, filename, true) {}
mode::mode(std::string path, system& system, std::string target_path,
std::string mode_symlink)
: resource(path), system_(system), mode_symlink_(mode_symlink),
target_path_(target_path) {}

bool mode::line(std::string new_line) {
return is_valid_mode(new_line) && line_resource::line(new_line);
/**
* get
* Handles the get operation on the /mode REST resource.
*/
mode::resp_type mode::get(resource::req_type) {
auto temp_mode = router_mode();
if (!temp_mode) {
auto msg = "Error: failed to find router mode.";
return internal_server_error(json::string(msg));
}

return ok(json::string(*temp_mode));
}

mode::resp_type mode::put(mode::req_type body) {
/**
* put
* Handles the 'put' operation on the /mode REST resource
*/
mode::resp_type mode::put(resource::req_type body) {
// Parse and validate the request body
json_t* root = body.get();
std::string old_mode_str;

// get the mode before we change it.
auto old_mode = router_mode();
if (old_mode) {
old_mode_str = *old_mode;
}

if (!json_is_string(root)) {
auto msg = "Error: request body is not a JSON string.";
Expand All @@ -32,11 +55,50 @@ mode::resp_type mode::put(mode::req_type body) {
std::string newval = json_string_value(root);

if (!is_valid_mode(newval)) {
auto msg = "Error: mode must is invalid.";
auto msg = "Error: mode is invalid.";
return bad_request(json::string(msg));
}

return line_resource::put(body);
if (newval != old_mode_str) {
// only need to update the resource if we have a new value
if (!router_mode(newval)) {
auto msg = "Error: failed to update resource.";
return internal_server_error(json::string(msg));
}
}
return get(json::null());
}

/**
* Returns the configured mode of the router card.
* Will be one of "passthrough", "secure-host", or "secure-lan".
*
* @returns The router card mode or None on an error.
*/
std::experimental::optional<std::string> mode::router_mode() {
auto fq_link = system_.symlink_target(mode_symlink_);
if (!fq_link) {
return {};
}

size_t period = fq_link->find_last_of('.');
size_t slash = fq_link->find_last_of('/');
std::string mode = fq_link->substr(slash + 1, period - slash - 1);
return {mode};
}

/**
* Sets the router card's configured mode.
*
* The change will be stored, but not take effect until a reboot.
*
* @param new_mode The mode to set. Valid input modes are: "passthrough",
* "secure-host", or "secure-lan".
* @returns true on success, false otherwise.
*/
bool mode::router_mode(std::string new_mode) {
std::string target_name = target_path_ + "/" + new_mode + ".target";
return system_.symlink_target(target_name, mode_symlink_);
}

} // namespace rest
Expand Down
28 changes: 24 additions & 4 deletions captived/src/rest/mode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,33 @@ namespace rest {
/**
* A resource for the router card mode.
*/
class mode : public line_resource {
class mode : public resource {
public:
mode(std::string path, system& system, std::string filename);

bool line(std::string new_line) override;
mode(std::string path, system& system, std::string target_path,
std::string mode_symlink);
~mode() override = default;

resp_type get(req_type) override;
resp_type put(req_type body) override;

/**
* Fetch the mode.
*
* @returns The mode or None on an error.
*/
virtual std::experimental::optional<std::string> router_mode();

/**
* Set the mode.
*
* @returns true on success and false on an error
*/
virtual bool router_mode(std::string new_mode);

protected:
system& system_;
std::string mode_symlink_; // fully qualified path to symlink we modify
std::string target_path_; // path to the systemd file where we point.
};

} // namespace rest
Expand Down
48 changes: 48 additions & 0 deletions captived/src/system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
#include <iostream>
#include <sstream>
#include <string>
#include <unistd.h>

#include <experimental/optional>
#include <sys/stat.h>

namespace captived {

Expand Down Expand Up @@ -92,6 +94,52 @@ class system {
return write(filename, line + '\n');
}

/**
* Return the target of a symbolic link.
*
* Note: Will only follow a single link and assumes argument IS a link
*/
std::experimental::optional<std::string>
symlink_target(std::string link_path) {
char buf[ROUTER_CARD_PATH_MAX];
std::string full_link_path = chroot_ + link_path;

ssize_t nbytes =
readlink(full_link_path.c_str(), buf, ROUTER_CARD_PATH_MAX);
if ((nbytes < 0) || (nbytes >= ROUTER_CARD_PATH_MAX)) { // error case
return {};
}

std::string target{buf, static_cast<size_t>(nbytes)};
return {target};
}

/**
* Add or change the symlink to a target.
*
* If a file already exists and is a symlink, it is deleted before a
* new link is created. If the file is not a link, error out.
*/
bool symlink_target(std::string target, std::string link) {
std::string fq_target = chroot_ + target;
std::string fq_link = chroot_ + link;

// check if file exists
struct stat buffer;
int stat_result = lstat(fq_link.c_str(), &buffer);
if (stat_result == 0) {
if (S_ISLNK(buffer.st_mode)) { // delete if a link
remove(fq_link.c_str());
} else { // error if other type of file
return false;
}
}

// create new symbolic link
int result = symlink(fq_target.c_str(), fq_link.c_str());
return (result == 0);
}

private:
std::string chroot_;
};
Expand Down