/// <inheritdoc/>
        public virtual Task <ListTenantsApiCall> PrepareListTenantsAsync(CancellationToken cancellationToken)
        {
            UriTemplate template = new UriTemplate("v2.0/tenants");
            Dictionary <string, string> parameters = new Dictionary <string, string>();

            Func <HttpResponseMessage, CancellationToken, Task <ReadOnlyCollectionPage <Tenant> > > deserializeResult =
                (responseMessage, innerCancellationToken) =>
            {
                Uri originalUri = responseMessage.RequestMessage.RequestUri;

                if (!HttpApiCall.IsAcceptable(responseMessage))
                {
                    throw new HttpWebException(responseMessage);
                }

                return(responseMessage.Content.ReadAsStringAsync()
                       .Select(
                           innerTask =>
                {
                    if (string.IsNullOrEmpty(innerTask.Result))
                    {
                        return null;
                    }

                    JObject responseObject = JsonConvert.DeserializeObject <JObject>(innerTask.Result);
                    JArray tenantsArray = responseObject["tenants"] as JArray;
                    if (tenantsArray == null)
                    {
                        return null;
                    }

                    IList <Tenant> list = tenantsArray.ToObject <Tenant[]>();
                    // http://docs.openstack.org/api/openstack-identity-service/2.0/content/Paginated_Collections-d1e325.html
                    Func <CancellationToken, Task <IHttpApiCall <ReadOnlyCollectionPage <Tenant> > > > prepareGetNextPageAsync = null;
                    JArray tenantsLinksArray = responseObject["tenants_links"] as JArray;
                    if (tenantsLinksArray != null)
                    {
                        IList <Link> tenantsLinks = tenantsLinksArray.ToObject <Link[]>();
                        Link nextLink = tenantsLinks.FirstOrDefault(i => string.Equals("next", i.Relation, StringComparison.OrdinalIgnoreCase));
                        if (nextLink != null)
                        {
                            prepareGetNextPageAsync =
                                nextCancellationToken =>
                            {
                                return PrepareListTenantsAsync(nextCancellationToken)
                                .WithUri(nextLink.Target)
                                .Select(_ => _.Result.AsHttpApiCall());
                            };
                        }
                    }

                    ReadOnlyCollectionPage <Tenant> results = new BasicReadOnlyCollectionPage <Tenant>(list, prepareGetNextPageAsync);
                    return results;
                }));
            };

            return(GetBaseUriAsync(cancellationToken)
                   .Then(PrepareRequestAsyncFunc(HttpMethod.Get, template, parameters, cancellationToken))
                   .Select(task => new ListTenantsApiCall(CreateCustomApiCall(task.Result, HttpCompletionOption.ResponseContentRead, deserializeResult))));
        }
        /// <inheritdoc/>
        public virtual Task <ListApiVersionsApiCall> PrepareListApiVersionsAsync(CancellationToken cancellationToken)
        {
            UriTemplate template = new UriTemplate(string.Empty);
            IDictionary <string, string> parameters = ImmutableDictionary <string, string> .Empty;

            Func <HttpResponseMessage, CancellationToken, Task <ReadOnlyCollectionPage <ApiVersion> > > deserializeResult =
                (responseMessage, innerCancellationToken) =>
            {
                Uri originalUri = responseMessage.RequestMessage.RequestUri;

                if (!HttpApiCall.IsAcceptable(responseMessage))
                {
                    throw new HttpWebException(responseMessage);
                }

                return(responseMessage.Content.ReadAsStringAsync()
                       .Select(
                           innerTask =>
                {
                    if (string.IsNullOrEmpty(innerTask.Result))
                    {
                        return null;
                    }

                    JObject responseObject = JsonConvert.DeserializeObject <JObject>(innerTask.Result);
                    JObject versionsObject = responseObject["versions"] as JObject;
                    if (versionsObject == null)
                    {
                        return null;
                    }

                    JArray versionsArray = versionsObject["values"] as JArray;
                    if (versionsArray == null)
                    {
                        return null;
                    }

                    IList <ApiVersion> list = versionsArray.ToObject <ApiVersion[]>();
                    // according to the available documentation, this call does not appear to be paginated
                    Func <CancellationToken, Task <IHttpApiCall <ReadOnlyCollectionPage <ApiVersion> > > > prepareGetNextPageAsync = null;

                    ReadOnlyCollectionPage <ApiVersion> results = new BasicReadOnlyCollectionPage <ApiVersion>(list, prepareGetNextPageAsync);
                    return results;
                }));
            };

            return(GetBaseUriAsync(cancellationToken)
                   .Then(PrepareRequestAsyncFunc(HttpMethod.Get, template, parameters, cancellationToken))
                   .Select(task => new ListApiVersionsApiCall(CreateCustomApiCall(task.Result, HttpCompletionOption.ResponseContentRead, deserializeResult))));
        }
        internal virtual void ValidateRequest(HttpRequestMessage request)
        {
            if (request == null)
            {
                throw new ArgumentNullException("request");
            }

            if (request.Headers.Accept != null && request.Headers.Accept.Count > 0)
            {
                MediaTypeHeaderValue[] acceptTypes = request.Headers.Accept.ToArray();
                MediaTypeHeaderValue   contentType = new MediaTypeHeaderValue("application/json");
                if (!acceptTypes.Any(acceptType => HttpApiCall.IsAcceptable(acceptType, contentType)))
                {
                    throw new NotSupportedException();
                }
            }
        }
        /// <inheritdoc/>
        /// <remarks>
        /// <para>Rackspace sends back the <c>media-types</c> property value wrapped in an additional JSON object, as
        /// opposed to the array OpenStack uses. This method overrides the behavior of the base OpenStack client to
        /// replace the unexpected structure with the correct layout prior to deserialization.</para>
        /// <note type="note">
        /// <para>If required, the original response is available via the <see cref="HttpResponseMessage.Content"/>
        /// property of the response.</para>
        /// </note>
        /// </remarks>
        public override Task <GetApiVersionApiCall> PrepareGetApiVersionAsync(ApiVersionId apiVersionId, CancellationToken cancellationToken)
        {
            UriTemplate template = new UriTemplate("{version_id}");
            Dictionary <string, string> parameters = new Dictionary <string, string> {
                { "version_id", apiVersionId.Value }
            };

            Func <HttpResponseMessage, CancellationToken, Task <ApiVersionResponse> > deserializeResult =
                (responseMessage, innerCancellationToken) =>
            {
                Uri originalUri = responseMessage.RequestMessage.RequestUri;

                if (!HttpApiCall.IsAcceptable(responseMessage))
                {
                    throw new HttpWebException(responseMessage);
                }

                return(responseMessage.Content.ReadAsStringAsync()
                       .Select(
                           innerTask =>
                {
                    if (string.IsNullOrEmpty(innerTask.Result))
                    {
                        return null;
                    }

                    JObject responseObject = JsonConvert.DeserializeObject <JObject>(innerTask.Result);
                    if (responseObject == null)
                    {
                        return null;
                    }

                    JObject versionObject = responseObject["version"] as JObject;
                    if (versionObject == null)
                    {
                        return responseObject.ToObject <ApiVersionResponse>();
                    }

                    JToken mediaTypesToken = versionObject["media-types"];
                    if (mediaTypesToken is JArray)
                    {
                        return responseObject.ToObject <ApiVersionResponse>();
                    }

                    JArray mediaTypesArray = mediaTypesToken["values"] as JArray;
                    if (mediaTypesArray == null)
                    {
                        versionObject.Remove("media-types");
                    }
                    else
                    {
                        versionObject["media-types"] = mediaTypesArray;
                    }

                    return responseObject.ToObject <ApiVersionResponse>();
                }));
            };

            return(GetBaseUriAsync(cancellationToken)
                   .Then(PrepareRequestAsyncFunc(HttpMethod.Get, template, parameters, cancellationToken))
                   .Select(task => new GetApiVersionApiCall(CreateCustomApiCall(task.Result, HttpCompletionOption.ResponseContentRead, deserializeResult))));
        }
        public HttpResponseMessage PostAuthenticate([FromBody] AuthenticationRequest authenticationRequest)
        {
            MediaTypeHeaderValue acceptedType = new MediaTypeHeaderValue("application/json");
            MediaTypeHeaderValue contentType  = Request.Content.Headers.ContentType;

            if (!HttpApiCall.IsAcceptable(acceptedType, contentType))
            {
                return(new HttpResponseMessage(HttpStatusCode.BadRequest));
            }

            ValidateRequest(Request);

            if (authenticationRequest == null)
            {
                return(new HttpResponseMessage(HttpStatusCode.BadRequest));
            }

            AuthenticationData authenticationData = authenticationRequest.AuthenticationData;

            if (authenticationData == null)
            {
                return(new HttpResponseMessage(HttpStatusCode.BadRequest));
            }

            if (authenticationData.TenantName != null &&
                !string.Equals(authenticationData.TenantName, _tenantName, StringComparison.OrdinalIgnoreCase))
            {
                return(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }

            if (authenticationData.TenantId != null &&
                authenticationData.TenantId != new ProjectId(_tenantId))
            {
                return(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }

            if (authenticationData.Token != null)
            {
                throw new NotImplementedException();
            }

            PasswordCredentials passwordCredentials = authenticationData.PasswordCredentials;

            if (passwordCredentials == null)
            {
                return(new HttpResponseMessage(HttpStatusCode.BadRequest));
            }

            if (!string.Equals(passwordCredentials.Username, _username, StringComparison.OrdinalIgnoreCase) ||
                !string.Equals(passwordCredentials.Password, _password, StringComparison.Ordinal))
            {
                return(new HttpResponseMessage(HttpStatusCode.Unauthorized));
            }

            bool hasTenant = authenticationData.TenantId != null || authenticationData.TenantName != null;

            string responseBody;

            if (hasTenant)
            {
                responseBody = IdentityServiceResources.AuthenticateResponseTemplate;
            }
            else
            {
                responseBody = IdentityServiceResources.AuthenticateWithoutTenantResponseTemplate;
            }

            lock (_lock)
            {
                var parameters = new Dictionary <string, string>();

                // expire the token 5 minutes early
                if (!_tokenExpires.HasValue || _tokenExpires < DateTimeOffset.Now - TimeSpan.FromMinutes(5))
                {
                    // generate a new token
                    _tokenCreated = DateTimeOffset.Now;
                    _tokenExpires = _tokenCreated + TimeSpan.FromHours(24);
                    _tokenId      = new TokenId(Guid.NewGuid().ToString());
                }

                parameters["issued_at"]    = JsonConvert.SerializeObject(_tokenCreated.Value);
                parameters["expires"]      = JsonConvert.SerializeObject(_tokenExpires.Value);
                parameters["tokenId"]      = JsonConvert.SerializeObject(_tokenId);
                parameters["tenantId"]     = JsonConvert.SerializeObject(_tenantId);
                parameters["tenantName"]   = JsonConvert.SerializeObject(_tenantName);
                parameters["username"]     = JsonConvert.SerializeObject(_username);
                parameters["userId"]       = JsonConvert.SerializeObject(_userId);
                parameters["userFullName"] = JsonConvert.SerializeObject(_userFullName);

                foreach (var pair in parameters)
                {
                    responseBody = responseBody.Replace("{" + pair.Key + "}", JsonConvert.DeserializeObject <string>(pair.Value));
                }
            }

            HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);

            result.Content = new StringContent(responseBody, Encoding.UTF8, "application/json");
            return(result);
        }