From 71385971da0612f87e8dcb87054b9b06138b703c Mon Sep 17 00:00:00 2001 From: Liam Hopkins Date: Tue, 9 Jul 2019 10:27:45 -0700 Subject: Groups (#784) * Initial support for OS Login groups. * getgrnam/getgrgid lookups --- .../src/include/oslogin_utils.h | 57 ++++- .../src/nss/nss_oslogin.cc | 115 +++++++-- .../google-compute-engine-oslogin/src/utils.cc | 278 ++++++++++++++++++--- .../test/oslogin_utils_test.cc | 253 ++++++++++++++----- 4 files changed, 569 insertions(+), 134 deletions(-) (limited to 'packages') diff --git a/packages/google-compute-engine-oslogin/src/include/oslogin_utils.h b/packages/google-compute-engine-oslogin/src/include/oslogin_utils.h index 6cd2024..ea8beae 100644 --- a/packages/google-compute-engine-oslogin/src/include/oslogin_utils.h +++ b/packages/google-compute-engine-oslogin/src/include/oslogin_utils.h @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include + #include #include @@ -29,7 +31,7 @@ namespace oslogin_utils { // Metadata server URL. static const char kMetadataServerUrl[] = - "http://metadata.google.internal/computeMetadata/v1/oslogin/"; + "http://metadata.google.internal/computeMetadata/v1/oslogin/"; // BufferManager encapsulates and manages a buffer and length. This class is not // thread safe. @@ -44,9 +46,11 @@ class BufferManager { // buffer for the string. bool AppendString(const string& value, char** buffer, int* errnop); + // Return a pointer to a buffer of size bytes. Returns NULL and sets errnop to + // ERANGE if there is not enough space left in the buffer for the request. + void* Reserve(size_t bytes, int* errnop); + private: - // Return a pointer to a buffer of size bytes. - void* Reserve(size_t bytes); // Whether there is space available in the buffer. bool CheckSpaceAvailable(size_t bytes_to_write) const; @@ -66,6 +70,12 @@ class Challenge { string status; }; +class Group { + public: + int64_t gid; + string name; +}; + // NssCache caches passwd entries for getpwent_r. This is used to prevent making // an HTTP call on every getpwent_r invocation. Stores up to cache_size entries // at a time. This class is not thread safe. @@ -98,7 +108,8 @@ class NssCache { // make an http call to the server if necessary to retrieve additional // entries. Returns whether passwd retrieval was successful. If true, the // passwd result will contain valid data. - bool NssGetpwentHelper(BufferManager* buf, struct passwd* result, int* errnop); + bool NssGetpwentHelper(BufferManager* buf, struct passwd* result, + int* errnop); // Returns the page token for requesting the next page of passwd entries. string GetPageToken() { return page_token_; } @@ -114,7 +125,7 @@ class NssCache { std::string page_token_; // Index for requesting the next passwd from the cache. - int index_; + uint32_t index_; // Whether the NssCache has reached the last page of the database. bool on_last_page_; @@ -144,8 +155,7 @@ class MutexLock { }; // Callback invoked when Curl completes a request. -size_t -OnCurlWrite(void* buf, size_t size, size_t nmemb, void* userp); +size_t OnCurlWrite(void* buf, size_t size, size_t nmemb, void* userp); // Uses Curl to issue a GET request to the given url. Returns whether the // request was successful. If successful, the result from the server will be @@ -163,17 +173,42 @@ std::string UrlEncode(const string& param); // Returns true if the given passwd contains valid fields. If pw_dir, pw_shell, // or pw_passwd are not set, this will populate these entries with default // values. -bool ValidatePasswd(struct passwd* result, BufferManager* buf, - int* errnop); +bool ValidatePasswd(struct passwd* result, BufferManager* buf, int* errnop); + +// Adds users and associated array of char* to provided buffer and store pointer +// to array in result.gr_mem. +bool AddUsersToGroup(std::vector users, struct group* result, + BufferManager* buf, int* errnop); + +// Iterates through all groups until one matching provided group is found, +// replacing gr_name with a buffermanager provided string. +bool FindGroup(struct group* grp, BufferManager* buf, int* errnop); + +// Iterates through all users for a group, storing results in a provided string +// vector. +bool GetUsersForGroup(string groupname, std::vector* users, + int* errnop); + +// Iterates through all groups for a user, storing results in a provided string +// vector. +bool GetGroupsForUser(string username, std::vector* groups, int* errnop); + +// Parses a JSON groups response, storing results in a provided Group vector. +bool ParseJsonToGroups(const string& json, std::vector* groups); + +// Parses a JSON users response, storing results in a provided string vector. +bool ParseJsonToUsers(const string& json, std::vector* users); // Parses a JSON LoginProfiles response for SSH keys. Returns a vector of valid // ssh_keys. A key is considered valid if it's expiration date is greater than // current unix time. std::vector ParseJsonToSshKeys(const string& json); +// Parses a JSON object and returns the value associated with a given key. +bool ParseJsonToKey(const string& json, const string& key, string* response); + // Parses a JSON LoginProfiles response and returns the email under the "name" // field. -bool ParseJsonToKey(const string& json, const string& key, string* email); bool ParseJsonToEmail(const string& json, string* email); // Parses a JSON LoginProfiles response and populates the passwd struct with the @@ -187,7 +222,7 @@ bool ParseJsonToPasswd(const string& response, struct passwd* result, bool ParseJsonToSuccess(const string& json); // Parses a JSON startSession response into a vector of Challenge objects. -bool ParseJsonToChallenges(const string& json, vector *challenges); +bool ParseJsonToChallenges(const string& json, vector* challenges); // Calls the startSession API. bool StartSession(const string& email, string* response); diff --git a/packages/google-compute-engine-oslogin/src/nss/nss_oslogin.cc b/packages/google-compute-engine-oslogin/src/nss/nss_oslogin.cc index 2f6245a..f928dcf 100644 --- a/packages/google-compute-engine-oslogin/src/nss/nss_oslogin.cc +++ b/packages/google-compute-engine-oslogin/src/nss/nss_oslogin.cc @@ -12,12 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include #include +#include #include #include +#include +#include #include #include #include @@ -26,18 +30,20 @@ #include #include -#include -#include - using std::string; +using oslogin_utils::AddUsersToGroup; using oslogin_utils::BufferManager; +using oslogin_utils::FindGroup; +using oslogin_utils::GetGroupsForUser; +using oslogin_utils::GetUsersForGroup; +using oslogin_utils::Group; using oslogin_utils::HttpGet; +using oslogin_utils::kMetadataServerUrl; using oslogin_utils::MutexLock; using oslogin_utils::NssCache; using oslogin_utils::ParseJsonToPasswd; using oslogin_utils::UrlEncode; -using oslogin_utils::kMetadataServerUrl; // Size of the NssCache. This also determines how many users will be requested // per HTTP call. @@ -52,8 +58,9 @@ static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER; extern "C" { // Get a passwd entry by id. -int _nss_oslogin_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, - size_t buflen, int *errnop) { +enum nss_status _nss_oslogin_getpwuid_r(uid_t uid, struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { BufferManager buffer_manager(buffer, buflen); std::stringstream url; url << kMetadataServerUrl << "users?uid=" << uid; @@ -77,8 +84,9 @@ int _nss_oslogin_getpwuid_r(uid_t uid, struct passwd *result, char *buffer, } // Get a passwd entry by name. -int _nss_oslogin_getpwnam_r(const char *name, struct passwd *result, - char *buffer, size_t buflen, int *errnop) { +enum nss_status _nss_oslogin_getpwnam_r(const char *name, struct passwd *result, + char *buffer, size_t buflen, + int *errnop) { BufferManager buffer_manager(buffer, buflen); std::stringstream url; url << kMetadataServerUrl << "users?username=" << UrlEncode(name); @@ -101,6 +109,70 @@ int _nss_oslogin_getpwnam_r(const char *name, struct passwd *result, return NSS_STATUS_SUCCESS; } +enum nss_status _nss_oslogin_getgrby(struct group *grp, char *buf, + size_t buflen, int *errnop) { + BufferManager buffer_manager(buf, buflen); + if (!FindGroup(grp, &buffer_manager, errnop)) + return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; + + std::vector users; + if (!GetUsersForGroup(grp->gr_name, &users, errnop)) + return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; + + if (!AddUsersToGroup(users, grp, &buffer_manager, errnop)) + return *errnop == ERANGE ? NSS_STATUS_TRYAGAIN : NSS_STATUS_NOTFOUND; + + return NSS_STATUS_SUCCESS; +} + +enum nss_status _nss_oslogin_getgrgid_r(gid_t gid, struct group *grp, char *buf, + size_t buflen, int *errnop) { + grp->gr_gid = gid; + return _nss_oslogin_getgrby(grp, buf, buflen, errnop); +} + +enum nss_status _nss_oslogin_getgrnam_r(const char *name, struct group *grp, + char *buf, size_t buflen, int *errnop) { + grp->gr_name = (char *)name; + return _nss_oslogin_getgrby(grp, buf, buflen, errnop); +} + +enum nss_status _nss_oslogin_initgroups_dyn(const char *user, gid_t skipgroup, + long int *start, long int *size, + gid_t **groupsp, long int limit, + int *errnop) { + std::vector grouplist; + if (!GetGroupsForUser(string(user), &grouplist, errnop)) { + return NSS_STATUS_NOTFOUND; + } + + gid_t *groups = *groupsp; + for (auto &group : grouplist) { + // Resize the buffer if needed. + if (*start == *size) { + gid_t *newgroups; + long int newsize = 2 * *size; + // Stop at limit if provided. + if (limit > 0) { + if (*size >= limit) { + *errnop = ERANGE; + return NSS_STATUS_TRYAGAIN; + } + newsize = MIN(limit, newsize); + } + newgroups = (gid_t *)realloc(groups, newsize * sizeof(gid_t *)); + if (newgroups == NULL) { + *errnop = EAGAIN; + return NSS_STATUS_TRYAGAIN; + } + *groupsp = groups = newgroups; + *size = newsize; + } + groups[(*start)++] = group.gid; + } + return NSS_STATUS_SUCCESS; +} + // nss_getpwent_r() is intentionally left unimplemented. This functionality is // now covered by the nss_cache binary and nss_cache module. @@ -113,19 +185,24 @@ NSS_METHOD_PROTOTYPE(__nss_compat_getpwuid_r); NSS_METHOD_PROTOTYPE(__nss_compat_getpwent_r); NSS_METHOD_PROTOTYPE(__nss_compat_setpwent); NSS_METHOD_PROTOTYPE(__nss_compat_endpwent); +NSS_METHOD_PROTOTYPE(__nss_compat_getgrnam_r); +NSS_METHOD_PROTOTYPE(__nss_compat_getgrgid_r); DECLARE_NSS_METHOD_TABLE(methods, - { NSDB_PASSWD, "getpwnam_r", __nss_compat_getpwnam_r, - (void*)_nss_oslogin_getpwnam_r }, - { NSDB_PASSWD, "getpwuid_r", __nss_compat_getpwuid_r, - (void*)_nss_oslogin_getpwuid_r }, - { NSDB_PASSWD, "getpwent_r", __nss_compat_getpwent_r, - (void*)_nss_oslogin_getpwent_r }, - { NSDB_PASSWD, "endpwent", __nss_compat_endpwent, - (void*)_nss_oslogin_endpwent }, - { NSDB_PASSWD, "setpwent", __nss_compat_setpwent, - (void*)_nss_oslogin_setpwent }, -) + {NSDB_PASSWD, "getpwnam_r", __nss_compat_getpwnam_r, + (void *)_nss_oslogin_getpwnam_r}, + {NSDB_PASSWD, "getpwuid_r", __nss_compat_getpwuid_r, + (void *)_nss_oslogin_getpwuid_r}, + {NSDB_PASSWD, "getpwent_r", __nss_compat_getpwent_r, + (void *)_nss_oslogin_getpwent_r}, + {NSDB_PASSWD, "endpwent", __nss_compat_endpwent, + (void *)_nss_oslogin_endpwent}, + {NSDB_PASSWD, "setpwent", __nss_compat_setpwent, + (void *)_nss_oslogin_setpwent}, + {NSDB_GROUP, "getgrnam_r", __nss_compat_getgrnam_r, + (void *)_nss_oslogin_getgrnam_r}, + {NSDB_GROUP, "getgrgid_r", __nss_compat_getgrgid_r, + (void *)_nss_oslogin_getgrgid_r}, ) NSS_REGISTER_METHODS(methods) } // extern "C" diff --git a/packages/google-compute-engine-oslogin/src/utils.cc b/packages/google-compute-engine-oslogin/src/utils.cc index 95f6c0b..ceda0af 100644 --- a/packages/google-compute-engine-oslogin/src/utils.cc +++ b/packages/google-compute-engine-oslogin/src/utils.cc @@ -15,18 +15,19 @@ // Requires libcurl4-openssl-dev libjson0 and libjson0-dev #include #include +#include #include #include #include #include + #include #include #include #if defined(__clang__) || __GNUC__ > 4 || \ - (__GNUC__ == 4 && (__GNUC_MINOR__ > 9 || \ - (__GNUC_MINOR__ == 9 && \ - __GNUC_PATCHLEVEL__ > 0))) + (__GNUC__ == 4 && \ + (__GNUC_MINOR__ > 9 || (__GNUC_MINOR__ == 9 && __GNUC_PATCHLEVEL__ > 0))) #include #define Regex std #else @@ -34,8 +35,8 @@ #define Regex boost #endif -#include #include +#include using std::string; @@ -53,11 +54,10 @@ BufferManager::BufferManager(char* buf, size_t buflen) bool BufferManager::AppendString(const string& value, char** buffer, int* errnop) { size_t bytes_to_write = value.length() + 1; - if (!CheckSpaceAvailable(bytes_to_write)) { - *errnop = ERANGE; + *buffer = static_cast(Reserve(bytes_to_write, errnop)); + if (*buffer == NULL) { return false; } - *buffer = static_cast(Reserve(bytes_to_write)); strncpy(*buffer, value.c_str(), bytes_to_write); return true; } @@ -69,11 +69,10 @@ bool BufferManager::CheckSpaceAvailable(size_t bytes_to_write) const { return true; } -void* BufferManager::Reserve(size_t bytes) { - if (buflen_ < bytes) { - std::cerr << "Attempted to reserve more bytes than the buffer can hold!" - << "\n"; - abort(); +void* BufferManager::Reserve(size_t bytes, int* errnop) { + if (!CheckSpaceAvailable(bytes)) { + *errnop = ERANGE; + return NULL; } void* result = buf_; buf_ += bytes; @@ -95,7 +94,7 @@ void NssCache::Reset() { } bool NssCache::HasNextPasswd() { - return index_ < passwd_cache_.size() && !passwd_cache_[index_].empty(); + return (index_ < passwd_cache_.size()) && !passwd_cache_[index_].empty(); } bool NssCache::GetNextPasswd(BufferManager* buf, passwd* result, int* errnop) { @@ -172,7 +171,7 @@ bool NssCache::NssGetpwentHelper(BufferManager* buf, struct passwd* result, response.empty() || !LoadJsonArrayToCache(response)) { // It is possible this to be true after LoadJsonArrayToCache(), so we // must check it again. - if(!OnLastPage()) { + if (!OnLastPage()) { *errnop = ENOENT; } return false; @@ -268,8 +267,7 @@ string UrlEncode(const string& param) { return encoded_param; } -bool ValidatePasswd(struct passwd* result, BufferManager* buf, - int* errnop) { +bool ValidatePasswd(struct passwd* result, BufferManager* buf, int* errnop) { // OS Login disallows uids less than 1000. if (result->pw_uid < 1000) { *errnop = EINVAL; @@ -307,6 +305,72 @@ bool ValidatePasswd(struct passwd* result, BufferManager* buf, return true; } +bool ParseJsonToUsers(const string& json, std::vector* result) { + json_object* root = NULL; + root = json_tokener_parse(json.c_str()); + if (root == NULL) { + return false; + } + + json_object* users = NULL; + if (!json_object_object_get_ex(root, "usernames", &users)) { + return false; + } + if (json_object_get_type(users) != json_type_array) { + return false; + } + for (int idx = 0; idx < json_object_array_length(users); idx++) { + json_object* user = json_object_array_get_idx(users, idx); + const char* username = json_object_get_string(user); + result->push_back(string(username)); + } + return true; +} + +bool ParseJsonToGroups(const string& json, std::vector* result) { + json_object* root = NULL; + root = json_tokener_parse(json.c_str()); + if (root == NULL) { + return false; + } + + json_object* groups = NULL; + if (!json_object_object_get_ex(root, "posixGroups", &groups)) { + return false; + } + if (json_object_get_type(groups) != json_type_array) { + return false; + } + for (int idx = 0; idx < json_object_array_length(groups); idx++) { + json_object* group = json_object_array_get_idx(groups, idx); + + json_object* gid; + if (!json_object_object_get_ex(group, "gid", &gid)) { + return false; + } + + json_object* name; + if (!json_object_object_get_ex(group, "name", &name)) { + return false; + } + + Group g; + g.gid = json_object_get_int64(gid); + // get_int64 will confusingly return 0 if the string can't be converted to + // an integer. We can't rely on type check as it may be a string in the API. + if (g.gid == 0) { + return false; + } + g.name = json_object_get_string(name); + if (g.name == "") { + return false; + } + + result->push_back(g); + } + return true; +} + std::vector ParseJsonToSshKeys(const string& json) { std::vector result; json_object* root = NULL; @@ -333,17 +397,14 @@ std::vector ParseJsonToSshKeys(const string& json) { if (json_object_get_type(ssh_public_keys) != json_type_object) { return result; } - json_object_object_foreach(ssh_public_keys, key, val) { - json_object* iter; - if (!json_object_object_get_ex(ssh_public_keys, key, &iter)) { - return result; - } - if (json_object_get_type(iter) != json_type_object) { + json_object_object_foreach(ssh_public_keys, key, obj) { + (void)(key); + if (json_object_get_type(obj) != json_type_object) { continue; } string key_to_add = ""; bool expired = false; - json_object_object_foreach(iter, key, val) { + json_object_object_foreach(obj, key, val) { string string_key(key); int val_type = json_object_get_type(val); if (string_key == "key") { @@ -470,6 +531,31 @@ bool ParseJsonToPasswd(const string& json, struct passwd* result, return ValidatePasswd(result, buf, errnop); } +bool AddUsersToGroup(std::vector users, struct group* result, + BufferManager* buf, int* errnop) { + if (users.size() < 1) { + return true; + } + + // Get some space for the char* array for number of users + 1 for NULL cap. + char** bufp; + if (!(bufp = + (char**)buf->Reserve(sizeof(char*) * (users.size() + 1), errnop))) { + return false; + } + result->gr_mem = bufp; + + for (auto& el : users) { + if (!buf->AppendString(el, bufp, errnop)) { + result->gr_mem = NULL; + return false; + } + } + *bufp = NULL; // End the array with a null pointer. + + return true; +} + bool ParseJsonToEmail(const string& json, string* email) { json_object* root = NULL; root = json_tokener_parse(json.c_str()); @@ -513,7 +599,7 @@ bool ParseJsonToKey(const string& json, const string& key, string* response) { const char* c_response; root = json_tokener_parse(json.c_str()); - if (root==NULL) { + if (root == NULL) { return false; } @@ -530,7 +616,7 @@ bool ParseJsonToKey(const string& json, const string& key, string* response) { } bool ParseJsonToChallenges(const string& json, - std::vector *challenges) { + std::vector* challenges) { json_object* root = NULL; root = json_tokener_parse(json.c_str()); @@ -538,7 +624,7 @@ bool ParseJsonToChallenges(const string& json, return false; } - json_object *jsonChallenges = NULL; + json_object* jsonChallenges = NULL; if (!json_object_object_get_ex(root, "challenges", &jsonChallenges)) { return false; } @@ -568,13 +654,134 @@ bool ParseJsonToChallenges(const string& json, return true; } +bool FindGroup(struct group* result, BufferManager* buf, int* errnop) { + if (result->gr_name == NULL && result->gr_gid == 0) { + return false; + } + std::stringstream url; + std::vector groups; + + string response; + long http_code; + string pageToken = ""; + + do { + url.str(""); + url << kMetadataServerUrl << "groups"; + if (pageToken != "") url << "?pageToken=" << pageToken; + + response.clear(); + http_code = 0; + if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || + response.empty()) { + *errnop = EAGAIN; + return false; + } + + if (!ParseJsonToKey(response, "nextPageToken", &pageToken)) { + pageToken = ""; + } + + groups.clear(); + if (!ParseJsonToGroups(response, &groups) || groups.empty()) { + *errnop = ENOENT; + return false; + } + + // Check for a match. + for (auto& el : groups) { + if ((result->gr_name != NULL) && (string(result->gr_name) == el.name)) { + // Set the name even though it matches because the final string must + // be stored in the provided buffer. + if (!buf->AppendString(el.name, &result->gr_name, errnop)) { + return false; + } + result->gr_gid = el.gid; + return true; + } + if ((result->gr_gid != 0) && (result->gr_gid == el.gid)) { + if (!buf->AppendString(el.name, &result->gr_name, errnop)) { + return false; + } + return true; + } + } + } while (pageToken != ""); + // Not found. + *errnop = ENOENT; + return false; +} + +bool GetGroupsForUser(string username, std::vector* groups, + int* errnop) { + std::stringstream url; + + string response; + long http_code; + string pageToken = ""; + + do { + url.str(""); + url << kMetadataServerUrl << "groups?username=" << username; + if (pageToken != "") url << "?pageToken=" << pageToken; + + response.clear(); + http_code = 0; + if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || + response.empty()) { + *errnop = EAGAIN; + return false; + } + + if (!ParseJsonToKey(response, "pageToken", &pageToken)) { + pageToken = ""; + } + + if (!ParseJsonToGroups(response, groups)) { + *errnop = ENOENT; + return false; + } + } while (pageToken != ""); + return true; +} + +bool GetUsersForGroup(string groupname, std::vector* users, + int* errnop) { + string response; + long http_code; + string pageToken = ""; + std::stringstream url; + + do { + url.str(""); + url << kMetadataServerUrl << "users?groupname=" << groupname; + if (pageToken != "") url << "?pageToken=" << pageToken; + + response.clear(); + http_code = 0; + if (!HttpGet(url.str(), &response, &http_code) || http_code != 200 || + response.empty()) { + *errnop = EAGAIN; + return false; + } + if (!ParseJsonToKey(response, "nextPageToken", &pageToken)) { + pageToken = ""; + } + if (!ParseJsonToUsers(response, users)) { + *errnop = EINVAL; + return false; + } + } while (pageToken != ""); + return true; +} + bool GetUser(const string& username, string* response) { std::stringstream url; url << kMetadataServerUrl << "users?username=" << UrlEncode(username); long http_code = 0; - if (!HttpGet(url.str(), response, &http_code) || response->empty() - || http_code != 200) { + if (!HttpGet(url.str(), response, &http_code) || response->empty() || + http_code != 200) { return false; } return true; @@ -601,8 +808,8 @@ bool StartSession(const string& email, string* response) { url << kMetadataServerUrl << "authenticate/sessions/start"; long http_code = 0; - if (!HttpPost(url.str(), data, response, &http_code) || response->empty() - || http_code != 200) { + if (!HttpPost(url.str(), data, response, &http_code) || response->empty() || + http_code != 200) { ret = false; } @@ -627,8 +834,7 @@ bool ContinueSession(bool alt, const string& email, const string& user_token, json_object_object_add(jobj, "action", json_object_new_string("START_ALTERNATE")); } else { - json_object_object_add(jobj, "action", - json_object_new_string("RESPOND")); + json_object_object_add(jobj, "action", json_object_new_string("RESPOND")); } // AUTHZEN type and START_ALTERNATE action don't provide credentials. @@ -643,11 +849,11 @@ bool ContinueSession(bool alt, const string& email, const string& user_token, data = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PLAIN); std::stringstream url; - url << kMetadataServerUrl << "authenticate/sessions/" - << session_id << "/continue"; + url << kMetadataServerUrl << "authenticate/sessions/" << session_id + << "/continue"; long http_code = 0; - if (!HttpPost(url.str(), data, response, &http_code) || response->empty() - || http_code != 200) { + if (!HttpPost(url.str(), data, response, &http_code) || response->empty() || + http_code != 200) { ret = false; } diff --git a/packages/google-compute-engine-oslogin/test/oslogin_utils_test.cc b/packages/google-compute-engine-oslogin/test/oslogin_utils_test.cc index 657b4e1..6c9c1df 100644 --- a/packages/google-compute-engine-oslogin/test/oslogin_utils_test.cc +++ b/packages/google-compute-engine-oslogin/test/oslogin_utils_test.cc @@ -13,10 +13,9 @@ // limitations under the License. // Requires libgtest-dev and gtest compiled and installed. -#include - #include #include +#include #include #include @@ -25,11 +24,10 @@ using std::vector; namespace oslogin_utils { - // Test that the buffer can successfully append multiple strings. TEST(BufferManagerTest, TestAppendString) { size_t buflen = 20; - char* buffer = (char*)malloc(buflen *sizeof(char)); + char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); char* first_string; @@ -38,7 +36,7 @@ TEST(BufferManagerTest, TestAppendString) { oslogin_utils::BufferManager buffer_manager(buffer, buflen); buffer_manager.AppendString("test1", &first_string, &test_errno); buffer_manager.AppendString("test2", &second_string, &test_errno); - EXPECT_EQ(test_errno, 0); + ASSERT_EQ(test_errno, 0); ASSERT_STREQ(first_string, "test1"); ASSERT_STREQ(second_string, "test2"); ASSERT_STREQ(buffer, "test1"); @@ -57,7 +55,7 @@ TEST(BufferManagerTest, TestAppendStringTooLarge) { oslogin_utils::BufferManager buffer_manager(buffer, buflen); ASSERT_FALSE( buffer_manager.AppendString("test1", &first_string, &test_errno)); - EXPECT_EQ(test_errno, ERANGE); + ASSERT_EQ(test_errno, ERANGE); } // Test successfully loading and retrieving an array of JSON posix accounts. @@ -87,9 +85,9 @@ TEST(NssCacheTest, TestLoadJsonArray) { // Verify that the first user was stored. ASSERT_TRUE(nss_cache.HasNextPasswd()); ASSERT_TRUE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); - EXPECT_EQ(test_errno, 0); - EXPECT_EQ(result.pw_uid, 1337); - EXPECT_EQ(result.pw_gid, 1337); + ASSERT_EQ(test_errno, 0); + ASSERT_EQ(result.pw_uid, 1337); + ASSERT_EQ(result.pw_gid, 1337); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); @@ -97,9 +95,9 @@ TEST(NssCacheTest, TestLoadJsonArray) { // Verify that the second user was stored. ASSERT_TRUE(nss_cache.HasNextPasswd()); ASSERT_TRUE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); - EXPECT_EQ(test_errno, 0); - EXPECT_EQ(result.pw_uid, 1338); - EXPECT_EQ(result.pw_gid, 1338); + ASSERT_EQ(test_errno, 0); + ASSERT_EQ(result.pw_uid, 1338); + ASSERT_EQ(result.pw_gid, 1338); ASSERT_STREQ(result.pw_name, "bar"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/bar"); @@ -107,7 +105,7 @@ TEST(NssCacheTest, TestLoadJsonArray) { // Verify that there are no more users stored. ASSERT_FALSE(nss_cache.HasNextPasswd()); ASSERT_FALSE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); - EXPECT_EQ(test_errno, ENOENT); + ASSERT_EQ(test_errno, ENOENT); } // Test successfully loading and retrieving a partial array. @@ -132,9 +130,9 @@ TEST(NssCacheTest, TestLoadJsonPartialArray) { // Verify that the first user was stored. ASSERT_TRUE(nss_cache.HasNextPasswd()); ASSERT_TRUE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); - EXPECT_EQ(test_errno, 0); - EXPECT_EQ(result.pw_uid, 1337); - EXPECT_EQ(result.pw_gid, 1337); + ASSERT_EQ(test_errno, 0); + ASSERT_EQ(result.pw_uid, 1337); + ASSERT_EQ(result.pw_gid, 1337); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); @@ -144,14 +142,13 @@ TEST(NssCacheTest, TestLoadJsonPartialArray) { // Verify that there are no more users stored. ASSERT_FALSE(nss_cache.HasNextPasswd()); ASSERT_FALSE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); - EXPECT_EQ(test_errno, ENOENT); + ASSERT_EQ(test_errno, ENOENT); } // Test successfully loading and retrieving the final response. TEST(NssCacheTest, TestLoadJsonFinalResponse) { NssCache nss_cache(2); - string response = - "{\"nextPageToken\": \"0\"}"; + string response = "{\"nextPageToken\": \"0\"}"; ASSERT_FALSE(nss_cache.LoadJsonArrayToCache(response)); ASSERT_EQ(nss_cache.GetPageToken(), ""); @@ -167,10 +164,9 @@ TEST(NssCacheTest, TestLoadJsonFinalResponse) { ASSERT_FALSE(nss_cache.HasNextPasswd()); ASSERT_TRUE(nss_cache.OnLastPage()); ASSERT_FALSE(nss_cache.GetNextPasswd(&buf, &result, &test_errno)); - EXPECT_EQ(test_errno, ENOENT); + ASSERT_EQ(test_errno, ENOENT); } - // Tests that resetting, and checking HasNextPasswd does not crash. TEST(NssCacheTest, ResetNullPtrTest) { NssCache nss_cache(2); @@ -192,8 +188,8 @@ TEST(ParseJsonPasswdTest, ParseJsonToPasswdSucceeds) { struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); - EXPECT_EQ(result.pw_uid, 1337); - EXPECT_EQ(result.pw_gid, 1338); + ASSERT_EQ(result.pw_uid, 1337); + ASSERT_EQ(result.pw_gid, 1338); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); @@ -213,8 +209,8 @@ TEST(ParseJsonPasswdTest, ParseJsonToPasswdSucceedsWithHighUid) { struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); - EXPECT_EQ(result.pw_uid, 4294967295); - EXPECT_EQ(result.pw_gid, 4294967295); + ASSERT_EQ(result.pw_uid, 4294967295); + ASSERT_EQ(result.pw_gid, 4294967295); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); @@ -233,8 +229,8 @@ TEST(ParseJsonPasswdTest, ParseJsonToPasswdSucceedsWithStringUid) { struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); - EXPECT_EQ(result.pw_uid, 1337); - EXPECT_EQ(result.pw_gid, 1338); + ASSERT_EQ(result.pw_uid, 1337); + ASSERT_EQ(result.pw_gid, 1338); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); @@ -253,8 +249,8 @@ TEST(ParseJsonPasswdTest, ParseJsonToPasswdNoLoginProfilesSucceeds) { struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); - EXPECT_EQ(result.pw_uid, 1337); - EXPECT_EQ(result.pw_gid, 1337); + ASSERT_EQ(result.pw_uid, 1337); + ASSERT_EQ(result.pw_gid, 1337); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); @@ -274,7 +270,7 @@ TEST(ParseJsonPasswdTest, ParseJsonToPasswdFailsWithERANGE) { struct passwd result; int test_errno = 0; ASSERT_FALSE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); - EXPECT_EQ(test_errno, ERANGE); + ASSERT_EQ(test_errno, ERANGE); } // Test parsing malformed JSON responses. @@ -297,13 +293,13 @@ TEST(ParseJsonPasswdTest, ParseJsonToPasswdFailsWithEINVAL) { struct passwd result; int test_errno = 0; ASSERT_FALSE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); - EXPECT_EQ(test_errno, EINVAL); + ASSERT_EQ(test_errno, EINVAL); // Reset errno. test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user2, &result, &buf, &test_errno)); - EXPECT_EQ(test_errno, 0); - EXPECT_EQ(result.pw_uid, 1337); - EXPECT_EQ(result.pw_gid, 1337); + ASSERT_EQ(test_errno, 0); + ASSERT_EQ(result.pw_uid, 1337); + ASSERT_EQ(result.pw_gid, 1337); } // Test parsing a partially filled response. Validate should fill empty fields @@ -321,8 +317,8 @@ TEST(ParseJsonPasswdTest, ValidatePartialJsonResponse) { struct passwd result; int test_errno = 0; ASSERT_TRUE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); - EXPECT_EQ(result.pw_uid, 1337); - EXPECT_EQ(result.pw_gid, 1337); + ASSERT_EQ(result.pw_uid, 1337); + ASSERT_EQ(result.pw_gid, 1337); ASSERT_STREQ(result.pw_name, "foo"); ASSERT_STREQ(result.pw_shell, "/bin/bash"); ASSERT_STREQ(result.pw_dir, "/home/foo"); @@ -343,7 +339,139 @@ TEST(ParseJsonPasswdTest, ValidateInvalidJsonResponse) { struct passwd result; int test_errno = 0; ASSERT_FALSE(ParseJsonToPasswd(test_user, &result, &buf, &test_errno)); - EXPECT_EQ(test_errno, EINVAL); + ASSERT_EQ(test_errno, EINVAL); +} + +// Test parsing a valid JSON response from the metadata server. +TEST(ParseJsonToGroupsTest, ParseJsonToGroupsSucceeds) { + string test_group = "{\"posixGroups\":[{\"name\":\"demo\",\"gid\":123452}]}"; + + std::vector groups; + ASSERT_TRUE(ParseJsonToGroups(test_group, &groups)); + ASSERT_EQ(groups[0].gid, 123452); + ASSERT_EQ(groups[0].name, "demo"); +} + +// Test parsing a valid JSON response from the metadata server with gid > 2^31. +TEST(ParseJsonToGroupsTest, ParseJsonToGroupsSucceedsWithHighGid) { + string test_group = + "{\"posixGroups\":[{\"name\":\"demo\",\"gid\":4294967295}]}"; + + std::vector groups; + ASSERT_TRUE(ParseJsonToGroups(test_group, &groups)); + ASSERT_EQ(groups[0].gid, 4294967295); + ASSERT_EQ(groups[0].name, "demo"); +} + +TEST(ParseJsonToGroupsTest, ParseJsonToGroupsSucceedsWithStringGid) { + string test_group = + "{\"posixGroups\":[{\"name\":\"demo\",\"gid\":\"123452\"}]}"; + + std::vector groups; + ASSERT_TRUE(ParseJsonToGroups(test_group, &groups)); + ASSERT_EQ(groups[0].gid, 123452); + ASSERT_EQ(groups[0].name, "demo"); +} + +// Test parsing malformed JSON responses. +TEST(ParseJsonToGroupsTest, ParseJsonToGroupsFails) { + string test_badgid = + "{\"posixGroups\":[{\"name\":\"demo\",\"gid\":\"this-should-be-int\"}]}"; + string test_nogid = "{\"posixGroups\":[{\"name\":\"demo\"}]}"; + string test_noname = "{\"posixGroups\":[{\"gid\":123452}]}"; + + std::vector groups; + ASSERT_FALSE(ParseJsonToGroups(test_badgid, &groups)); + ASSERT_FALSE(ParseJsonToGroups(test_nogid, &groups)); + ASSERT_FALSE(ParseJsonToGroups(test_noname, &groups)); +} + +// Test parsing a valid JSON response from the metadata server. +TEST(ParseJsonToUsersTest, ParseJsonToUsersSucceeds) { + string test_group_users = + "{\"usernames\":[\"user0001\",\"user0002\",\"user0003\",\"user0004\"," + "\"user0005\"]}"; + + std::vector users; + ASSERT_TRUE(ParseJsonToUsers(test_group_users, &users)); + ASSERT_FALSE(users.empty()); + ASSERT_EQ(users.size(), 5); + + ASSERT_EQ(users[0], "user0001"); + ASSERT_EQ(users[1], "user0002"); + ASSERT_EQ(users[2], "user0003"); + ASSERT_EQ(users[3], "user0004"); + ASSERT_EQ(users[4], "user0005"); +} + +// Test parsing a valid JSON response from the metadata server. +TEST(ParseJsonToUsersTest, ParseJsonToUsersEmptyGroupSucceeds) { + string test_group_users = "{\"usernames\":[]}"; + + std::vector users; + ASSERT_TRUE(ParseJsonToUsers(test_group_users, &users)); + ASSERT_TRUE(users.empty()); +} + +// Test parsing malformed JSON responses. +TEST(ParseJsonToUsersTest, ParseJsonToUsersFails) { + string test_group_users = + "{\"badstuff\":[\"user0001\",\"user0002\",\"user0003\",\"user0004\"," + "\"user0005\"]}"; + + std::vector users; + ASSERT_FALSE(ParseJsonToUsers(test_group_users, &users)); +} + +TEST(GetUsersForGroupTest, GetUsersForGroupSucceeds) { + string response; + long http_code; + ASSERT_TRUE( + HttpGet("http://metadata.google.internal/reset", &response, &http_code)); + + std::vector users; + int errnop = 0; + + ASSERT_TRUE(GetUsersForGroup("demo", &users, &errnop)); + ASSERT_FALSE(users.empty()); + ASSERT_EQ(users[0], "user000173_grande_focustest_org"); + ASSERT_EQ(errnop, 0); +} + +TEST(FindGroupTest, FindGroupByGidSucceeds) { + string response; + long http_code; + ASSERT_TRUE( + HttpGet("http://metadata.google.internal/reset", &response, &http_code)); + + size_t buflen = 200 * sizeof(char); + char* buffer = (char*)malloc(buflen); + ASSERT_STRNE(buffer, NULL); + BufferManager buf(buffer, buflen); + int errnop = 0; + + struct group grp = {}; + grp.gr_gid = 123452; + ASSERT_TRUE(FindGroup(&grp, &buf, &errnop)); + ASSERT_EQ(errnop, 0); +} + +TEST(FindGroupTest, FindGroupByNameSucceeds) { + string response; + long http_code; + ASSERT_TRUE( + HttpGet("http://metadata.google.internal/reset", &response, &http_code)); + + size_t buflen = 200 * sizeof(char); + char* buffer = (char*)malloc(buflen); + ASSERT_STRNE(buffer, NULL); + BufferManager buf(buffer, buflen); + int errnop; + + const char* match = "demo"; + struct group grp = {}; + grp.gr_name = (char*)match; + ASSERT_TRUE(FindGroup(&grp, &buf, &errnop)); } TEST(ParseJsonEmailTest, SuccessfullyParsesEmail) { @@ -372,10 +500,9 @@ TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysSucceeds) { char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); - int test_errno = 0; std::vector result = ParseJsonToSshKeys(test_user); - EXPECT_EQ(result.size(), 1); - EXPECT_EQ(result[0], "test_key"); + ASSERT_EQ(result.size(), 1); + ASSERT_EQ(result[0], "test_key"); } TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysMultipleKeys) { @@ -388,11 +515,10 @@ TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysMultipleKeys) { char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); - int test_errno = 0; std::vector result = ParseJsonToSshKeys(test_user); - EXPECT_EQ(result.size(), 2); - EXPECT_EQ(result[0], "test_key"); - EXPECT_EQ(result[1], "test_key2"); + ASSERT_EQ(result.size(), 2); + ASSERT_EQ(result[0], "test_key"); + ASSERT_EQ(result[1], "test_key2"); } TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysFiltersExpiredKeys) { @@ -405,10 +531,9 @@ TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysFiltersExpiredKeys) { char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); - int test_errno = 0; std::vector result = ParseJsonToSshKeys(test_user); - EXPECT_EQ(result.size(), 1); - EXPECT_EQ(result[0], "test_key"); + ASSERT_EQ(result.size(), 1); + ASSERT_EQ(result[0], "test_key"); } TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysFiltersMalformedExpiration) { @@ -421,10 +546,9 @@ TEST(ParseJsonSshKeyTest, ParseJsonToSshKeysFiltersMalformedExpiration) { char* buffer = (char*)malloc(buflen * sizeof(char)); ASSERT_STRNE(buffer, NULL); BufferManager buf(buffer, buflen); - int test_errno = 0; std::vector result = ParseJsonToSshKeys(test_user); - EXPECT_EQ(result.size(), 1); - EXPECT_EQ(result[0], "test_key"); + ASSERT_EQ(result.size(), 1); + ASSERT_EQ(result[0], "test_key"); } TEST(ParseJsonAuthorizeSuccess, SuccessfullyAuthorized) { @@ -433,16 +557,8 @@ TEST(ParseJsonAuthorizeSuccess, SuccessfullyAuthorized) { } TEST(ValidateUserNameTest, ValidateValidUserNames) { - string cases[] = { - "user", - "_", - ".", - ".abc_", - "_abc-", - "ABC", - "A_.-", - "ausernamethirtytwocharacterslong" - }; + string cases[] = {"user", "_", ".", ".abc_", + "_abc-", "ABC", "A_.-", "ausernamethirtytwocharacterslong"}; for (auto test_user : cases) { ASSERT_TRUE(ValidateUserName(test_user)); } @@ -482,29 +598,30 @@ TEST(ParseJsonKeyTest, TestMissingKey) { } TEST(ParseJsonChallengesTest, TestChallenges) { - string challenges_json = "{\"status\":\"CHALLENGE_REQUIRED\",\"sessionId\":" + string challenges_json = + "{\"status\":\"CHALLENGE_REQUIRED\",\"sessionId\":" "\"testSessionId\",\"challenges\":[{\"challengeId\":1,\"challengeType\":" "\"TOTP\",\"status\":\"READY\"}, {\"challengeId\":2,\"challengeType\":" "\"AUTHZEN\",\"status\":\"PROPOSED\"}]}"; vector challenges; ASSERT_TRUE(ParseJsonToChallenges(challenges_json, &challenges)); - EXPECT_EQ(challenges.size(), 2); - EXPECT_EQ(challenges[0].id, 1); - EXPECT_EQ(challenges[0].type, "TOTP"); + ASSERT_EQ(challenges.size(), 2); + ASSERT_EQ(challenges[0].id, 1); + ASSERT_EQ(challenges[0].type, "TOTP"); } TEST(ParseJsonChallengesTest, TestMalformedChallenges) { - string challenges_json = "{\"status\":\"CHALLENGE_REQUIRED\",\"sessionId\":" + string challenges_json = + "{\"status\":\"CHALLENGE_REQUIRED\",\"sessionId\":" "\"testSessionId\",\"challenges\":[{\"challengeId\":1,\"challengeType\":" "\"TOTP\",\"status\":\"READY\"}, {\"challengeId\":2,\"challengeType\":" "\"AUTHZEN\"}]}"; vector challenges; ASSERT_FALSE(ParseJsonToChallenges(challenges_json, &challenges)); - EXPECT_EQ(challenges.size(), 1); + ASSERT_EQ(challenges.size(), 1); } } // namespace oslogin_utils - -int main(int argc, char **argv) { +int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } -- cgit v1.2.1