/// <summary>Get latest swagger definitions from cloud.</summary> /// <param name="config">Contains config info, including profile to use</param> /// <returns>Success or failure</returns> public bool GetSwagger(CentrifyCliConfig config) { try { Ccli.ConditionalWrite($"Fetching latest swagger definitions from cloud.", config.Silent); // Doesn't require auth InitializeClient(config.Profile.URL); var runTask = PlaceCall("/vfslow/lib/api/swagger.json", ""); runTask.Wait(); Tuple <Runner.ResultCode, string> callResult = runTask.Result; if (callResult.Item1 == Runner.ResultCode.Success) { // Write item2 to swagger.json file. There's no JSON to process. using (StreamWriter file = File.CreateText(config.SwaggerDirectory)) { file.Write(callResult.Item2); } return(true); } else { Ccli.WriteErrorText($"Error fetching swagger definitions from cloud: {callResult}"); } } catch (Exception e) { Ccli.WriteErrorText($"Exception fetching swagger definitions from cloud: {e.Message}"); } return(false); }
/// <summary>Load server profiles.</summary> /// <param name="fileName">File to load server profiles from</param> /// <param name="silent">Process logging disabled</param> /// <returns>Number of profiles loaded</returns> public int LoadServerList(string fileName, bool silent) { if (File.Exists(fileName)) { try { string importJson = System.IO.File.ReadAllText(fileName); ServerList = JsonConvert.DeserializeObject <Dictionary <string, ServerProfile> >(importJson); } catch (Exception e) { Ccli.ConditionalWrite($"Error parsing Server Profiles from {fileName}: {e.Message}", silent); return(-1); } } return(ServerList.Count); }
/// <summary>Centrify-specific OAuth2 Token request</summary> /// <param name="config">Contains config info, including profile to use</param> /// <param name="timeout">Timeout is milliserconds for REST calls</param> /// <returns>Tuple of success and either token or error message.</returns> public async Task <Tuple <ResultCode, string> > OAuth2_GenerateTokenRequest(CentrifyCliConfig config, int timeout) { ServerProfile profile = config.Profile; string TokenEndpoint = "/oauth2/token/" + profile.OAuthAppId; if (profile.Password == null && !config.Silent) { Console.Write($"Enter password for {profile.UserName}: "); profile.Password = ReadMaskedPassword(false); } string queryParams = $"grant_type=client_credentials&response_type=code&state={RandomString(15)}&scope=ccli&client_id={profile.UserName}&client_secret={profile.Password}"; try { Ccli.ConditionalWrite($"Requesting token for {profile.UserName}.", config.Silent); InitializeClient(profile.URL); Task <HttpResponseMessage> response = m_restUpdater.NewRequestAsync(profile.URL + TokenEndpoint, queryParams); if (!response.Wait(timeout)) { return(new Tuple <ResultCode, string>(ResultCode.Timeout, "Request for token timed out.")); } HttpStatusCode statusCode = response.Result.StatusCode; string contents = await response.Result.Content.ReadAsStringAsync(); JObject resultSet = ParseContentsToJObject(contents); if (response.Result.StatusCode != HttpStatusCode.OK) { return(new Tuple <ResultCode, string>(ResultCode.Failed, $"Token request failed/denied: {statusCode}, {ResultToString(resultSet)}")); } if (!TryGetJObjectValue(resultSet, "access_token", out string accessToken)) { throw new ArgumentException($"Token response is missing 'access_token': {ResultToString(resultSet)}", "access_token"); } return(new Tuple <ResultCode, string>(ResultCode.Success, accessToken)); } catch (Exception ex) { return(new Tuple <ResultCode, string>(ResultCode.Exception, ex.Message)); } }
/// <summary>Loads the swagger.json file from, typically, depot2\Cloud\Lib\Api\swagger.json /// Builds API Resource from it.</summary> /// <param name="config">Contains config info, including profile to use</param> /// <returns>Success or failure</returns> public bool LoadSwagger(CentrifyCliConfig config) { string swaggerPath = config.SwaggerDirectory; if (String.IsNullOrEmpty(swaggerPath)) { Ccli.WriteErrorText("No swagger path defined in config."); return(false); } Ccli.ConditionalWrite($"Loading swagger definitions from {swaggerPath}", config.Silent); bool exists = File.Exists(swaggerPath); if ((!exists) || (File.GetCreationTimeUtc(swaggerPath) < (DateTime.UtcNow - SWAGGER_REFRESH))) { // Fetch from cloud if no swagger or swagger is 'old' if (!GetSwagger(config)) { if (exists) { Ccli.ConditionalWrite($"Using existing swagger defintiions.", config.Silent); } else { Ccli.WriteErrorText($"No swagger definitions available from cloud."); return(false); } } } JObject swagSet = null; try { using (StreamReader file = File.OpenText(swaggerPath)) using (JsonTextReader reader = new JsonTextReader(file)) { swagSet = (JObject)JToken.ReadFrom(reader); } } catch (Exception e) { Ccli.WriteErrorText($"Error loading swagger definitions from {swaggerPath}: {e.Message}"); return(false); } JArray calls = new JArray(); foreach (JProperty path in swagSet["paths"]) { JProperty restPath = new JProperty("path", (string)path.Name); JProperty group = new JProperty("group", (string)path.First["post"]["tags"].First); string[] pathBits = ((string)path.Name).Split(new char[] { '/' }); JProperty desc = new JProperty("api", pathBits[pathBits.Length - 1]); JProperty reference = new JProperty("reference", (string)path.First["post"]["summary"]); string parameters = "{"; int paramCount = 0; JToken pathParams = path.First["post"]["parameters"].First; if (pathParams != null) { try { foreach (JProperty prop in pathParams["schema"]["properties"]) { if (paramCount++ > 0) { parameters += ",\n"; } parameters += " \"" + (string)prop.Name + "\": \"\""; } } catch { try { foreach (JToken tok in pathParams.First) { if (tok is JProperty prop) { if (paramCount++ > 0) { parameters += ",\n"; } parameters += " \"" + (string)prop + "\": \"\""; } } } catch (Exception e) { Ccli.WriteErrorText($"Error parsing swagger properties from {swaggerPath}: {e.Message}"); return(false); }; } } if (paramCount > 0) { parameters += "\n"; } parameters += "}"; JProperty sample = new JProperty("sample", parameters); JObject thisCall = new JObject { restPath, // path == REST endpoint sample, // parameters group, // Grouping of calls reference, // Reference (not really API, misnamed) desc // Name of call }; calls.Add(thisCall); } m_apiCalls = new JObject(); JProperty callWrapper = new JProperty("apis", calls); m_apiCalls.Add(callWrapper); return(true); }
/// <summary>Authenticate the user via username/password/etc.<para/> /// OAuth token is the preferred method of authenication; this exists for ease of use</summary> /// <param name="config">Contains config info, including profile to use</param> /// <param name="timeout">Timeout is milliserconds for REST calls</param> /// <returns>Tuple of success or failure reason and message or results.</returns> public Tuple <ResultCode, string> Authenticate(CentrifyCliConfig config, int timeout) { ServerProfile profile = config.Profile; try { if (string.IsNullOrWhiteSpace(profile.UserName)) { return(new Tuple <ResultCode, string>(ResultCode.NotFound, $"No user in config to authenticate.")); } InitializeClient(profile.URL); // Do StartAuthentication string endpoint = "/Security/StartAuthentication"; Dictionary <string, string> args = new Dictionary <string, string>() { { "User", profile.UserName }, { "Version", "1.0" } }; var authTask = PlaceCall(endpoint, JsonConvert.SerializeObject(args)); if (!authTask.Wait(timeout)) { return(new Tuple <ResultCode, string>(ResultCode.Timeout, $"Request timed out ({endpoint}).")); } Tuple <Runner.ResultCode, string> callResult = authTask.Result; if (callResult.Item1 != ResultCode.Success) { return(new Tuple <ResultCode, string>(ResultCode.Failed, MakeFailResult(callResult.Item2, $"Authentication request failed ({endpoint}): {callResult.Item1}"))); } // Remember session and tenant JObject resultSet = ParseContentsToJObject(callResult.Item2); if (!TryGetJObjectValue(resultSet, "Result", out JObject results)) { throw new ArgumentException($"Authentication results have no 'result' property ({endpoint}):\n{ResultToString(resultSet)}", "Result"); } if (TryGetJObjectValue(results, "Redirect", out string redirect)) { Ccli.ConditionalWrite($"Authentication is redirected, use the prefered URL: {redirect}", config.Silent); return(new Tuple <ResultCode, string>(ResultCode.Redirected, redirect)); } if (!TryGetJObjectValue(results, "SessionId", out string sessionId)) { throw new ArgumentException($"Authentication results are missing 'SessionId' ({endpoint}): {ResultToString(results)}", "SessionId"); } if (!TryGetJObjectValue(results, "TenantId", out string tenantId)) { throw new ArgumentException($"Authentication results are missing 'TenantId' ({endpoint}): {ResultToString(results)}", "TenantId"); } if (!TryGetJObjectValue(results, "Challenges", out JToken challenges)) { throw new ArgumentException($"Authentication results are missing 'Challenges' ({endpoint}): {ResultToString(results)}", "Challenges"); } // If pw was supplied, and is one of the first mechs, use what was supplied automagically int passwordMechIdx = -1; if (profile.Password != null) { // Present the option(s) to the user for (int mechIdx = 0; mechIdx < challenges[0]["Mechanisms"].Children().Count() && passwordMechIdx == -1; mechIdx++) { if (challenges[0]["Mechanisms"][mechIdx]["Name"].Value <string>() == "UP") { passwordMechIdx = mechIdx; } } } // Need to satisfy at least 1 challenge in each collection: for (int x = 0; x < challenges.Children().Count(); x++) { int optionSelect = -1; // If passwordMechIdx is set, we should auto-fill password first, do it if (passwordMechIdx == -1) { // Present the option(s) to the user for (int mechIdx = 0; mechIdx < challenges[x]["Mechanisms"].Children().Count(); mechIdx++) { Console.WriteLine("Option {0}: {1}", mechIdx, MechToDescription(challenges[x]["Mechanisms"][mechIdx])); } if (challenges[x]["Mechanisms"].Children().Count() == 1) { optionSelect = 0; } else { while (optionSelect < 0 || optionSelect > challenges[x]["Mechanisms"].Children().Count()) { Console.Write("Select option and press enter: "); string optEntered = Console.ReadLine(); int.TryParse(optEntered, out optionSelect); } } } else { optionSelect = passwordMechIdx; passwordMechIdx = -1; } try { var newChallenges = AdvanceForMech(timeout, tenantId, sessionId, challenges[x]["Mechanisms"][optionSelect], profile); if (newChallenges != null) { challenges = newChallenges; x = -1; } } catch (Exception ex) { return(new Tuple <ResultCode, string>(ResultCode.Failed, ex.Message)); } } } catch (Exception e) { Exception i = e; string allMessages = ""; // De-dup; sometimes they double up. while (i != null) { if (!allMessages.Contains(i.Message)) { allMessages += i.Message + " " + Environment.NewLine; } i = i.InnerException; } return(new Tuple <ResultCode, string>(ResultCode.Exception, allMessages)); } m_url = profile.URL; return(new Tuple <ResultCode, string>(ResultCode.Success, "")); }