Using our own hardware, we've installed vanilla openstack with all components however I am having problems accessing services other than the Identity due to a region issue. The code used is as follows called with the admin account and admin tennant we created...
public static void TestAccess(string userName, string password, string projectName, string projectId)
{
try
{
Uri baseUrl = new Uri(URL_IDENTITY);
CloudIdentityWithProject projectCloudId = new CloudIdentityWithProject();
projectCloudId.Username = userName;
projectCloudId.Password = password;
projectCloudId.ProjectName = projectName;
projectCloudId.ProjectId = new ProjectId(projectId);
OpenStackIdentityProvider idProvider = new OpenStackIdentityProvider(baseUrl, projectCloudId);
UserAccess userAccess = idProvider.Authenticate(projectCloudId);
IEnumerable<ExtendedEndpoint> eps = idProvider.ListEndpoints(userAccess.Token.Id);
string reg = idProvider.DefaultRegion; // This is null
ServiceCatalog[] scs = userAccess.ServiceCatalog;
// Get the list of regions
regionList = new List<string>();
foreach (ServiceCatalog sc in scs)
{
foreach (Endpoint ep in sc.Endpoints)
{
regionList.Add(ep.Region); // This is 'regionOne' in every case
}
}
// Try stuff...
foreach(string region in regionList.Distinct())
{
// Get a list of containers
CloudFilesProvider cfp = new CloudFilesProvider(projectCloudId, idProvider);
// THIS LINE FAILS
IEnumerable<Container> listOfContainers = cfp.ListContainers(region: region);
foreach (Container ctnr in listOfContainers)
{
Console.WriteLine("Container: {0}", ctnr.Name);
}
CloudNetworksProvider cnp = new CloudNetworksProvider(identity: null, identityProvider: idProvider);
IEnumerable<CloudNetwork> networks = cnp.ListNetworks(identity: null, region: region);
foreach (CloudNetwork network in networks)
{
Console.WriteLine("Network[{0}] name: {1}", networkCount, network.Label);
Console.WriteLine("Network[{0}] Id: {1}", networkCount, network.Id);
++networkCount;
}
Console.WriteLine("{0} networks listed.", networkCount);
}
}
catch(Exception ex)
{
throw;
}
}
The code fails at the call to ListContainers(region: region) with the error... 'The user does not have access to the requested service or region' where as if I don't specify a region the error is simply 'No region was provided, the service does not provide a region-independent endpoint, and there is no default region set for the user's account'
We are only accessing our internal network at the moment so regions aren't important to us yet...
Also of note is that when making a call to...
CloudNetwork detail = cnp.ShowNetwork(networkGuid, "regionOne");
of a network I can see returns the error 'The item was not found or does not exist.'
Help and advice much appreciated.
I managed to extend the functionality of the Openstack.Net SDK fairly simply. The code below extends it to include various functions for Tenant/Project manipulations...
Firstly, create a NewTenant container that will be used to pass data to and from the webservices, I've put it in the same namespace as the others...
using Newtonsoft.Json;
namespace net.openstack.Core.Domain
{
[JsonObject(MemberSerialization.OptIn)]
public class NewTenant
{
/// <summary>
/// Gets the ID for the new user.
/// <note type="warning">The value of this property is not defined. Do not use.</note>
/// </summary>
[JsonProperty("id", DefaultValueHandling = DefaultValueHandling.Include)]
public string Id { get; private set; }
[JsonProperty("name")]
public string Name { get; private set; }
[JsonProperty("description")]
public string Description { get; private set; }
[JsonProperty("enabled")]
public bool Enabled { get; private set; }
public NewTenant(string name, string description, bool enabled = true)
{
Name = name;
Description = description;
Enabled = enabled;
}
}
}
Now we can create any new Request classes for posting the data...
using System;
using Newtonsoft.Json;
using net.openstack.Core.Domain;
namespace net.openstack.Core.Request
{
[JsonObject(MemberSerialization.OptIn)]
internal class AddTenantRequest
{
[JsonProperty("tenant")]
public NewTenant Tenant { get; private set; }
public AddTenantRequest(NewTenant tenant)
{
if (tenant == null)
throw new ArgumentNullException("tenant");
Tenant = tenant;
}
}
}
Now create the Response objects for the requests to help retrieve the data
using net.openstack.Core.Domain;
using Newtonsoft.Json;
namespace net.openstack.Core.Response
{
[JsonObject(MemberSerialization.OptIn)]
internal class NewTenantResponse
{
[JsonProperty("tenant")]
public NewTenant NewTenant { get; private set; }
}
[JsonObject(MemberSerialization.OptIn)]
internal class TenantResponse
{
[JsonProperty("tenant")]
public Tenant Tenant { get; private set; }
}
}
Now we can create a class that inherits from OpenStackIdentityProvider with the additional functionality for Tenant/Project manipulation that we want...
using System;
using System.Net;
using JSIStudios.SimpleRESTServices.Client;
using net.openstack.Core.Domain;
using net.openstack.Core.Request;
using net.openstack.Core.Response;
namespace net.openstack.Core.Providers
{
public class ExtendedOpenStackIdentityProvider : OpenStackIdentityProvider
{
public ExtendedOpenStackIdentityProvider(Uri urlBase)
: base(urlBase)
{
}
public ExtendedOpenStackIdentityProvider(Uri urlBase, CloudIdentity identity)
: base(urlBase, identity)
{
}
public ExtendedOpenStackIdentityProvider(Uri urlBase, JSIStudios.SimpleRESTServices.Client.IRestService restService, net.openstack.Core.Caching.ICache<UserAccess> tokenCache)
: base(urlBase, restService, tokenCache)
{
}
public ExtendedOpenStackIdentityProvider(Uri urlBase, CloudIdentity identity, JSIStudios.SimpleRESTServices.Client.IRestService restService, net.openstack.Core.Caching.ICache<UserAccess> tokenCache)
: base(urlBase, identity, restService, tokenCache)
{
}
public NewTenant AddTenant(NewTenant tenant, CloudIdentity identity)
{
if (tenant == null)
throw new ArgumentNullException("tenant");
if (string.IsNullOrEmpty(tenant.Name))
throw new ArgumentException("tenant.Name cannot be null or empty");
if (tenant.Id != null)
throw new InvalidOperationException("tenant.Id must be null");
CheckIdentity(identity);
var response = ExecuteRESTRequest<NewTenantResponse>(identity, new Uri(UrlBase, "/v2.0/tenants"), HttpMethod.POST, new AddTenantRequest(tenant));
if (response == null || response.Data == null)
return null;
return response.Data.NewTenant;
}
public Tenant GetTenant(string tenantId, CloudIdentity identity)
{
if (tenantId == null)
throw new ArgumentNullException("tenantId");
CheckIdentity(identity);
var urlPath = string.Format("v2.0/tenants/{0}", tenantId);
var response = ExecuteRESTRequest<TenantResponse>(identity, new Uri(UrlBase, urlPath), HttpMethod.GET);
if (response == null || response.Data == null)
return null;
return response.Data.Tenant;
}
public bool DeleteTenant(string tenantId, CloudIdentity identity)
{
if (tenantId == null)
throw new ArgumentNullException("tenantId");
if (string.IsNullOrEmpty(tenantId))
throw new ArgumentException("tenantId cannot be empty");
CheckIdentity(identity);
var urlPath = string.Format("v2.0/tenants/{0}", tenantId);
var response = ExecuteRESTRequest(identity, new Uri(UrlBase, urlPath), HttpMethod.DELETE);
if (response != null && response.StatusCode == HttpStatusCode.NoContent)
return true;
return false;
}
public bool AddTenantUserRole(string tenantId, string userId, string roleId, CloudIdentity identity)
{
if (tenantId == null)
throw new ArgumentNullException("tenantId");
if (string.IsNullOrEmpty(tenantId))
throw new ArgumentException("tenantId cannot be empty");
if (userId == null)
throw new ArgumentNullException("userId");
if (string.IsNullOrEmpty(userId))
throw new ArgumentException("userId cannot be empty");
if (roleId == null)
throw new ArgumentNullException("roleId");
if (string.IsNullOrEmpty(roleId))
throw new ArgumentException("roleId cannot be empty");
CheckIdentity(identity);
var urlPath = string.Format("v2.0/tenants/{0}/users/{1}/roles/OS-KSADM/{2}", tenantId, userId, roleId);
var response = ExecuteRESTRequest(identity, new Uri(UrlBase, urlPath), HttpMethod.PUT);
if (response != null && response.StatusCode == HttpStatusCode.NoContent)
return true;
return false;
}
}
}
I imagine that this functionality will appear in the GitHub version soon, but if not I hope it's useful.
Related
I have been running into a rather frustrating issue. I am attempting to authenticate a user against an Active Directory, and in order to do so I pass my users variables into the following class.
public static ILdapAuthentication CreateInstance(string domainAndUser, string password, string ldapPath)
{
string[] dllPaths = Directory.GetFiles(ExecutingAssemblyDirectory, "*.dll");
List<Assembly> listOfAssemblies = new List<Assembly>();
foreach (var dllPath in dllPaths.Where(x => x.Contains("ActiveDirectoryAuthentication")))
{
Assembly assembly = Assembly.LoadFrom(dllPath);
listOfAssemblies.Add(assembly);
}
Type type = null;
int foundTypes = 0;
foreach (var assembly in listOfAssemblies)
{
type =
assembly.GetTypes()
.FirstOrDefault(x => x.GetInterfaces().Any(i => i == typeof(ILdapAuthentication)));
if (type == null)
continue;
foundTypes++;
}
if (foundTypes == 0)
throw new Exception("ActiveDirectoryAuthentication DLL not found.");
if (foundTypes > 1)
throw new Exception("Only one ActiveDirectoryAuthentication DLL must be used.");
return Activator.CreateInstance(type, domainAndUser, password, ldapPath) as ILdapAuthentication;
}
The issue occurs in the foreach loop, as I attempt to get my Types, it always returns null, and doesn't even hit the Interface (ILDPAuthentication) code below.
public interface ILdapAuthentication
{
bool IsActiveDirectoryUserValid();
}
which invokes the following code:
public class LdapAuthentication : ILdapAuthentication
{
private string DomainAndUser { get; set; }
private string Password { get; set; }
private string LdapPath { get; set; }
public LdapAuthentication(string domainAndUser, string password, string ldapPath)
{
this.DomainAndUser = domainAndUser;
this.Password = password;
this.LdapPath = ldapPath;
}
public bool IsActiveDirectoryUserValid()
{
try
{
if (!this.DomainAndUser.Contains('\\'))
throw new Exception("Domain User is invalid.");
string[] userLogin = this.DomainAndUser.Split('\\');
string domain = userLogin[0];
string userName = userLogin[1];
DirectoryEntry entry = new DirectoryEntry(this.LdapPath, this.DomainAndUser, this.Password);
object obj = entry.NativeObject;
DirectorySearcher search = new DirectorySearcher(entry);
search.Filter = "(SAMAccountName=" + userName + ")";
search.PropertiesToLoad.Add("CN");
SearchResult result = search.FindOne();
if (null == result)
{
return false;
}
else
{
return true;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
}
}
The initial class looks for the DLLs in my application folder called ActiveDirectoryAuthentication which I have copied in.
I have seen this before - types in a explicitly loaded assembly do not match types in a referenced project. Because you are using:
i == typeof(ILdapAuthentication)
you are reliant on the equality comparison for the Type class, which may not return equality when you are expecting it to. I suggest instead you do:
i.FullName == typeof(ILdapAuthentication).FullName
which will use a simple string comparison.
I am trying to implement a generic caller that uses OpenWeatherMap's different weather API's, but I got stuck in regards to how I would put in the right identifier for the link.
.../weather?q=... returns JSON data for the current weather;
.../forecast?q=... returns JSON data for a five day forecast.
I am looking for the textbook way to maybe retrieve the API type of each class through accessing GetAPIType(), cast that to an int and put it in the index, so that I would be able to use identifiers[index]. Or perhaps there is an easier way to do it.
Checking for the typeof(T) also crossed my mind, and I would assign the index depending on the if(typeof(T).Equals(typeof(...))) construct, but that seems very messy and if OpenWeatherMap had 100 API's in theory, I would need 100 different if constructs. With this in mind, wouldn't creating those checks beat the purpose of Client being generic?
A third solution I thought of would be passing APIType type as a parameter for the Client constructor,
e.g. var client = new Client<CurrentWeatherDTO>(APIType.CurrentWeather, location, apiKey),
but given the fact that Client is generic and I already provide a type when I instantiate it, it would seem awfully redundant.
Client.cs
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Reflection;
namespace Rainy.OpenWeatherMapAPI
{
public class Client<T>
{
private readonly string location;
private readonly string apiKey;
private readonly string requestUri;
private readonly string[] identifiers = { "weather", "forecast" };
private readonly int index;
public Client(string location, string apiKey)
{
// Get the type of API used in order to get the right identifier for the link.
// ??? Maybe use Reflection, somehow.
this.location = location;
this.apiKey = apiKey;
requestUri = $"api.openweathermap.org/data/2.5/{}?q={location}&appid={apiKey}";
}
public async Task<T> GetWeather(CancellationToken cancellationToken)
{
using (var client = new HttpClient())
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
var stream = await response.Content.ReadAsStreamAsync();
if (response.IsSuccessStatusCode)
return DeserializeJsonFromStream<T>(stream);
var content = await StreamToStringAsync(stream);
throw new APIException
{
StatusCode = (int)response.StatusCode,
Content = content
};
}
}
private U DeserializeJsonFromStream<U>(Stream stream)
{
if (stream == null || stream.CanRead == false)
return default(U);
using (var sr = new StreamReader(stream))
using (var jtr = new JsonTextReader(sr))
{
var js = new JsonSerializer();
var searchResult = js.Deserialize<U>(jtr);
return searchResult;
}
}
private async Task<string> StreamToStringAsync(Stream stream)
{
string content = null;
if (stream != null)
using (var sr = new StreamReader(stream))
content = await sr.ReadToEndAsync();
return content;
}
}
}
APIType.cs
namespace Rainy.OpenWeatherMapAPI
{
public enum APIType
{
CurrentWeather = 0,
FiveDayForecast = 1
}
}
IWeather.cs
namespace Rainy.OpenWeatherMapAPI
{
public interface IWeather
{
APIType GetAPIType();
}
}
CurrentWeatherDTO.cs
namespace Rainy.OpenWeatherMapAPI.CurrentWeatherData
{
class CurrentWeatherDTO : IWeather
{
public APIType GetAPIType()
{
return APIType.CurrentWeather;
}
}
}
FiveDayForecastDTO.cs
namespace Rainy.OpenWeatherMapAPI.WeatherForecastData
{
class FiveDayForecastDTO : IWeather
{
public APIType GetAPIType()
{
return APIType.FiveDayForecast;
}
}
}
I would not use an enum to drive the index of an array.
I would directly return the string in a static way.
This solution can also work with the index of the array if you want.
Here is the code and the dotnetfiddle:
using System;
public class Program
{
public static void Main()
{
var client1 = new Client<CurrentWeatherDTO>(null);
Console.WriteLine("Client CurrentWeather type: " + client1.Type);
var client2 = new Client<FiveDayForecastDTO>(null);
Console.WriteLine("Client FiveDay type: " + client2.Type);
}
public class Client<T> where T : IWeather, new()
{
public string Type { get; set; }
public Client(string apiKey)
{
var dto = (IWeather)new T();
this.Type = dto.GetAPIType();
}
}
public static class APIType
{
public static string CurrentWeather = "weather";
public static string FiveDayForecast = "forecast";
}
public interface IWeather
{
string GetAPIType();
}
class CurrentWeatherDTO : IWeather
{
public string GetAPIType()
{
return APIType.CurrentWeather;
}
}
class FiveDayForecastDTO : IWeather
{
public string GetAPIType()
{
return APIType.FiveDayForecast;
}
}
}
I would probably use a solution like this, but maybe a bit more error handling.
There's a couple of references for how to use HttpClient.
I don't really understand the part in the requestUri with {}, maybe that's part of your problem, I changed it to {???} in my sample code.
class Client
{
// Problems using HttpClient and look into using IHttpClientFactory...
// http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html
// https://www.hanselman.com/blog/HttpClientFactoryForTypedHttpClientInstancesInASPNETCore21.aspx
static HttpClient _httpClient = new HttpClient();
readonly string WeatherUri = $"api.openweathermap.org/data/2.5/{???}?q={0}&appid={1}";
public async Task<T> GetWeather<T>(string location, CancellationToken cancellationToken)
{
var apiKey = ApiKeyAttribute.GetApiKey<T>();
if (apiKey == null) throw new Exception("ApiKeyAttirbute missing");
var requestUri = string.Format(WeatherUri, location, apiKey);
return await GetItem<T>(requestUri, cancellationToken);
}
public async Task<T> GetItem<T>(string requestUri, CancellationToken cancellationToken)
{
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
var response = await _httpClient.SendAsync(httpRequestMessage, cancellationToken);
if (!response.IsSuccessStatusCode) throw new Exception("Error requesting data");
if (response.Content == null) return default(T);
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(content);
}
}
[ApiKeyAttribute("weather")]
class CurrentWeatherDTO { /* add appropriat properties */ }
[ApiKeyAttribute("forecast")]
class FiveDayForecastDTO { /* add appropriat properties */ }
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
class ApiKeyAttribute : Attribute
{
public string Name { get; private set; }
public ApiKeyAttribute(string name)
{
Name = name;
}
public static string GetApiKey<T>()
{
var attribute = typeof(T).GetCustomAttribute<ApiKeyAttribute>();
return attribute?.Name;
}
}
I've searched on google on how to dynamically edit the Resources.Values.strings.xml file so I can add my Facebook Access Token so the user won't need to log in again when he reuses the app.
Is it possible to edit it, or do I have to use another method to store the token?
I think the best place would be to save this to the KeyStore. To do this you must use the DependencyService More Info here
The interface and implementation would then be:
PCL interface
public interface IAuth
{
void CreateStore();
IEnumerable<string> FindAccountsForService(string serviceId);
void Save(string pin,string serviceId);
void Delete(string serviceId);
}
Android
public class IAuthImplementation : IAuth
{
Context context;
KeyStore ks;
KeyStore.PasswordProtection prot;
static readonly object fileLock = new object();
const string FileName = "MyProg.Accounts";
static readonly char[] Password = null;
public void CreateStore()
{
this.context = Android.App.Application.Context;
ks = KeyStore.GetInstance(KeyStore.DefaultType);
prot = new KeyStore.PasswordProtection(Password);
try
{
lock (fileLock)
{
using (var s = context.OpenFileInput(FileName))
{
ks.Load(s, Password);
}
}
}
catch (Java.IO.FileNotFoundException)
{
//ks.Load (null, Password);
LoadEmptyKeyStore(Password);
}
}
public IEnumerable<string> FindAccountsForService(string serviceId)
{
var r = new List<string>();
var postfix = "-" + serviceId;
var aliases = ks.Aliases();
while (aliases.HasMoreElements)
{
var alias = aliases.NextElement().ToString();
if (alias.EndsWith(postfix))
{
var e = ks.GetEntry(alias, prot) as KeyStore.SecretKeyEntry;
if (e != null)
{
var bytes = e.SecretKey.GetEncoded();
var password = System.Text.Encoding.UTF8.GetString(bytes);
r.Add(password);
}
}
}
return r;
}
public void Delete(string serviceId)
{
var alias = MakeAlias(serviceId);
ks.DeleteEntry(alias);
Save();
}
public void Save(string pin, string serviceId)
{
var alias = MakeAlias(serviceId);
var secretKey = new SecretAccount(pin);
var entry = new KeyStore.SecretKeyEntry(secretKey);
ks.SetEntry(alias, entry, prot);
Save();
}
void Save()
{
lock (fileLock)
{
using (var s = context.OpenFileOutput(FileName, FileCreationMode.Private))
{
ks.Store(s, Password);
}
}
}
static string MakeAlias(string serviceId)
{
return "-" + serviceId;
}
class SecretAccount : Java.Lang.Object, ISecretKey
{
byte[] bytes;
public SecretAccount(string password)
{
bytes = System.Text.Encoding.UTF8.GetBytes(password);
}
public byte[] GetEncoded()
{
return bytes;
}
public string Algorithm
{
get
{
return "RAW";
}
}
public string Format
{
get
{
return "RAW";
}
}
}
static IntPtr id_load_Ljava_io_InputStream_arrayC;
void LoadEmptyKeyStore(char[] password)
{
if (id_load_Ljava_io_InputStream_arrayC == IntPtr.Zero)
{
id_load_Ljava_io_InputStream_arrayC = JNIEnv.GetMethodID(ks.Class.Handle, "load", "(Ljava/io/InputStream;[C)V");
}
IntPtr intPtr = IntPtr.Zero;
IntPtr intPtr2 = JNIEnv.NewArray(password);
JNIEnv.CallVoidMethod(ks.Handle, id_load_Ljava_io_InputStream_arrayC, new JValue[]
{
new JValue (intPtr),
new JValue (intPtr2)
});
JNIEnv.DeleteLocalRef(intPtr);
if (password != null)
{
JNIEnv.CopyArray(intPtr2, password);
JNIEnv.DeleteLocalRef(intPtr2);
}
}
Call Create Store in the main activity of Android app first. - This could possibly be improved and remove CreateStrore() from the interface by checking if ks == null in Save and Delete and calling the method if true
This would then save the access token to the KeyStore that you can then retrieve later
I have a Xamarin application and have managed to download my data from my server to my device. I have also got it set up so that it can take a SqlCipher Encryption key to encrypt the data.
My question is where is the correct location to store my key that I use to encrypt this data? Is it to you KeyStore / KeyChain? Which mono classes should I be looking to use?
Due to the popularity of this question I am going to post my implementation of this:
PCL interface
public interface IAuth
{
void CreateStore();
IEnumerable<string> FindAccountsForService(string serviceId);
void Save(string pin,string serviceId);
void Delete(string serviceId);
}
Android
public class IAuthImplementation : IAuth
{
Context context;
KeyStore ks;
KeyStore.PasswordProtection prot;
static readonly object fileLock = new object();
const string FileName = "MyProg.Accounts";
static readonly char[] Password = null;
public void CreateStore()
{
this.context = Android.App.Application.Context;
ks = KeyStore.GetInstance(KeyStore.DefaultType);
prot = new KeyStore.PasswordProtection(Password);
try
{
lock (fileLock)
{
using (var s = context.OpenFileInput(FileName))
{
ks.Load(s, Password);
}
}
}
catch (Java.IO.FileNotFoundException)
{
//ks.Load (null, Password);
LoadEmptyKeyStore(Password);
}
}
public IEnumerable<string> FindAccountsForService(string serviceId)
{
var r = new List<string>();
var postfix = "-" + serviceId;
var aliases = ks.Aliases();
while (aliases.HasMoreElements)
{
var alias = aliases.NextElement().ToString();
if (alias.EndsWith(postfix))
{
var e = ks.GetEntry(alias, prot) as KeyStore.SecretKeyEntry;
if (e != null)
{
var bytes = e.SecretKey.GetEncoded();
var password = System.Text.Encoding.UTF8.GetString(bytes);
r.Add(password);
}
}
}
return r;
}
public void Delete(string serviceId)
{
var alias = MakeAlias(serviceId);
ks.DeleteEntry(alias);
Save();
}
public void Save(string pin, string serviceId)
{
var alias = MakeAlias(serviceId);
var secretKey = new SecretAccount(pin);
var entry = new KeyStore.SecretKeyEntry(secretKey);
ks.SetEntry(alias, entry, prot);
Save();
}
void Save()
{
lock (fileLock)
{
using (var s = context.OpenFileOutput(FileName, FileCreationMode.Private))
{
ks.Store(s, Password);
}
}
}
static string MakeAlias(string serviceId)
{
return "-" + serviceId;
}
class SecretAccount : Java.Lang.Object, ISecretKey
{
byte[] bytes;
public SecretAccount(string password)
{
bytes = System.Text.Encoding.UTF8.GetBytes(password);
}
public byte[] GetEncoded()
{
return bytes;
}
public string Algorithm
{
get
{
return "RAW";
}
}
public string Format
{
get
{
return "RAW";
}
}
}
static IntPtr id_load_Ljava_io_InputStream_arrayC;
void LoadEmptyKeyStore(char[] password)
{
if (id_load_Ljava_io_InputStream_arrayC == IntPtr.Zero)
{
id_load_Ljava_io_InputStream_arrayC = JNIEnv.GetMethodID(ks.Class.Handle, "load", "(Ljava/io/InputStream;[C)V");
}
IntPtr intPtr = IntPtr.Zero;
IntPtr intPtr2 = JNIEnv.NewArray(password);
JNIEnv.CallVoidMethod(ks.Handle, id_load_Ljava_io_InputStream_arrayC, new JValue[]
{
new JValue (intPtr),
new JValue (intPtr2)
});
JNIEnv.DeleteLocalRef(intPtr);
if (password != null)
{
JNIEnv.CopyArray(intPtr2, password);
JNIEnv.DeleteLocalRef(intPtr2);
}
}
Call Create Store in the main activity of Android app first. - This could possibly be improved and remove CreateStrore() from the interface by checking if ks == null in Save and Delete and calling the method if true
iOS
public class IAuthImplementation : IAuth
{
public IEnumerable<string> FindAccountsForService(string serviceId)
{
var query = new SecRecord(SecKind.GenericPassword);
query.Service = serviceId;
SecStatusCode result;
var records = SecKeyChain.QueryAsRecord(query, 1000, out result);
return records != null ?
records.Select(GetAccountFromRecord).ToList() :
new List<string>();
}
public void Save(string pin, string serviceId)
{
var statusCode = SecStatusCode.Success;
var serializedAccount = pin;
var data = NSData.FromString(serializedAccount, NSStringEncoding.UTF8);
//
// Remove any existing record
//
var existing = FindAccount(serviceId);
if (existing != null)
{
var query = new SecRecord(SecKind.GenericPassword);
query.Service = serviceId;
statusCode = SecKeyChain.Remove(query);
if (statusCode != SecStatusCode.Success)
{
throw new Exception("Could not save account to KeyChain: " + statusCode);
}
}
//
// Add this record
//
var record = new SecRecord(SecKind.GenericPassword);
record.Service = serviceId;
record.Generic = data;
record.Accessible = SecAccessible.WhenUnlocked;
statusCode = SecKeyChain.Add(record);
if (statusCode != SecStatusCode.Success)
{
throw new Exception("Could not save account to KeyChain: " + statusCode);
}
}
public void Delete(string serviceId)
{
var query = new SecRecord(SecKind.GenericPassword);
query.Service = serviceId;
var statusCode = SecKeyChain.Remove(query);
if (statusCode != SecStatusCode.Success)
{
throw new Exception("Could not delete account from KeyChain: " + statusCode);
}
}
string GetAccountFromRecord(SecRecord r)
{
return NSString.FromData(r.Generic, NSStringEncoding.UTF8);
}
string FindAccount(string serviceId)
{
var query = new SecRecord(SecKind.GenericPassword);
query.Service = serviceId;
SecStatusCode result;
var record = SecKeyChain.QueryAsRecord(query, out result);
return record != null ? GetAccountFromRecord(record) : null;
}
public void CreateStore()
{
throw new NotImplementedException();
}
}
WP
public class IAuthImplementation : IAuth
{
public IEnumerable<string> FindAccountsForService(string serviceId)
{
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
string[] auths = store.GetFileNames("MyProg");
foreach (string path in auths)
{
using (var stream = new BinaryReader(new IsolatedStorageFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, store)))
{
int length = stream.ReadInt32();
byte[] data = stream.ReadBytes(length);
byte[] unprot = ProtectedData.Unprotect(data, null);
yield return Encoding.UTF8.GetString(unprot, 0, unprot.Length);
}
}
}
}
public void Save(string pin, string serviceId)
{
byte[] data = Encoding.UTF8.GetBytes(pin);
byte[] prot = ProtectedData.Protect(data, null);
var path = GetAccountPath(serviceId);
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
using (var stream = new IsolatedStorageFileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, store))
{
stream.WriteAsync(BitConverter.GetBytes(prot.Length), 0, sizeof(int)).Wait();
stream.WriteAsync(prot, 0, prot.Length).Wait();
}
}
public void Delete(string serviceId)
{
var path = GetAccountPath(serviceId);
using (var store = IsolatedStorageFile.GetUserStoreForApplication())
{
store.DeleteFile(path);
}
}
private string GetAccountPath(string serviceId)
{
return String.Format("{0}", serviceId);
}
public void CreateStore()
{
throw new NotImplementedException();
}
}
This is an adaptation of the Xamarin.Auth library (Found Here) but removes the dependency from the Xamarin.Auth library to provide cross platform use through the interface in the PCL. For this reason I have simplified it to only save one string. This is probably not the best implementation but it works in my case. Feel free to expand upon this
There is a nuget package called KeyChain.NET that encapsulated this logic for iOs, Android and Windows Phone.
It's open source and you have find sample at its github repository
More info at this blog post
would anyone have a working example of an amazon ITEMLOOKUP ?>
i have the following code but it does not seem to work:
string ISBN = "0393326381";
string ASIN = "";
if (!(string.IsNullOrEmpty(ISBN) && string.IsNullOrEmpty(ASIN)))
{
AWSECommerceServicePortTypeChannel service = new AWSECommerceServicePortTypeChannel();
ItemLookup lookup = new ItemLookup();
ItemLookupRequest request = new ItemLookupRequest();
lookup.AssociateTag = secretKey;
lookup.AWSAccessKeyId = accessKeyId;
if (string.IsNullOrEmpty(ASIN))
{
request.IdType = ItemLookupRequestIdType.ISBN;
request.ItemId = new string[] { ISBN.Replace("-", "") };
}
else
{
request.IdType = ItemLookupRequestIdType.ASIN;
request.ItemId = new string[] { ASIN };
}
request.ResponseGroup = new string[] { "OfferSummary" };
lookup.Request = new ItemLookupRequest[] { request };
response = service.ItemLookup(lookup);
if (response.Items.Length > 0 && response.Items[0].Item.Length > 0)
{
Item item = response.Items[0].Item[0];
if (item.MediumImage == null)
{
//bookImageHyperlink.Visible = false;
}
else
{
//bookImageHyperlink.ImageUrl = item.MediumImage.URL;
}
//bookImageHyperlink.NavigateUrl = item.DetailPageURL;
//bookTitleHyperlink.Text = item.ItemAttributes.Title;
//bookTitleHyperlink.NavigateUrl = item.DetailPageURL;
if (item.OfferSummary.LowestNewPrice == null)
{
if (item.OfferSummary.LowestUsedPrice == null)
{
//priceHyperlink.Visible = false;
}
else
{
//priceHyperlink.Text = string.Format("Buy used {0}", item.OfferSummary.LowestUsedPrice.FormattedPrice);
//priceHyperlink.NavigateUrl = item.DetailPageURL;
}
}
else
{
//priceHyperlink.Text = string.Format("Buy new {0}", item.OfferSummary.LowestNewPrice.FormattedPrice);
//priceHyperlink.NavigateUrl = item.DetailPageURL;
}
if (item.ItemAttributes.Author != null)
{
//authorLabel.Text = string.Format("By {0}", string.Join(", ", item.ItemAttributes.Author));
}
else
{
//authorLabel.Text = string.Format("By {0}", string.Join(", ", item.ItemAttributes.Creator.Select(c => c.Value).ToArray()));
}
/*
ItemLink link = item.ItemLinks.Where(i => i.Description.Contains("Wishlist")).FirstOrDefault();
if (link == null)
{
//wishListHyperlink.Visible = false;
}
else
{
//wishListHyperlink.NavigateUrl = link.URL;
}
* */
}
}
}
the problem is with this:
thisshould be defined differently but i do not know how AWSECommerceServicePortTypeChannel service = new AWSECommerceServicePortTypeChannel();
Say, that code looks awful familiar. You're missing the Endpoint signing piece from when they switched over to requiring that you add message signing. You need to add a behavior on your client. Here's the change to your code above:
if (!(string.IsNullOrEmpty(ISBN) && string.IsNullOrEmpty(ASIN)))
{
AWSECommerceServicePortTypeClient client = new AWSECommerceServicePortTypeClient();
client.ChannelFactory.Endpoint.Behaviors.Add(
new Amazon.AmazonSigningEndpointBehavior(
accessKeyId,
secretKey);
ItemLookup lookup = new ItemLookup();
ItemLookupRequest request = new ItemLookupRequest();
lookup.AssociateTag = accessKeyId;
lookup.AWSAccessKeyId = secretKey;
//... etc.
And here's the Endpoint (I can't take credit for this, I wish I could remember who should):
namespace Amazon
{
public class AmazonSigningEndpointBehavior : IEndpointBehavior {
private string accessKeyId = "";
private string secretKey = "";
public AmazonSigningEndpointBehavior(string accessKeyId, string secretKey) {
this.accessKeyId = accessKeyId;
this.secretKey = secretKey;
}
public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, ClientRuntime clientRuntime) {
clientRuntime.MessageInspectors.Add(new AmazonSigningMessageInspector(accessKeyId, secretKey));
}
public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, EndpointDispatcher endpointDispatcher) { return; }
public void Validate(ServiceEndpoint serviceEndpoint) { return; }
public void AddBindingParameters(ServiceEndpoint serviceEndpoint, BindingParameterCollection bindingParameters) { return; }
}
}
Oh. And you'll need the MessageInspector for that to work.
namespace Amazon
{
public class AmazonSigningMessageInspector : IClientMessageInspector {
private string accessKeyId = "";
private string secretKey = "";
public AmazonSigningMessageInspector(string accessKeyId, string secretKey) {
this.accessKeyId = accessKeyId;
this.secretKey = secretKey;
}
public object BeforeSendRequest(ref Message request, IClientChannel channel) {
// prepare the data to sign
string operation = Regex.Match(request.Headers.Action, "[^/]+$").ToString();
DateTime now = DateTime.UtcNow;
string timestamp = now.ToString("yyyy-MM-ddTHH:mm:ssZ");
string signMe = operation + timestamp;
byte[] bytesToSign = Encoding.UTF8.GetBytes(signMe);
// sign the data
byte[] secretKeyBytes = Encoding.UTF8.GetBytes(secretKey);
HMAC hmacSha256 = new HMACSHA256(secretKeyBytes);
byte[] hashBytes = hmacSha256.ComputeHash(bytesToSign);
string signature = Convert.ToBase64String(hashBytes);
// add the signature information to the request headers
request.Headers.Add(new AmazonHeader("AWSAccessKeyId", accessKeyId));
request.Headers.Add(new AmazonHeader("Timestamp", timestamp));
request.Headers.Add(new AmazonHeader("Signature", signature));
return null;
}
public void AfterReceiveReply(ref Message reply, object correlationState) { }
}
}
And finally, the Header:
namespace Amazon
{
public class AmazonHeader : MessageHeader
{
private string name;
private string value;
public AmazonHeader(string name, string value)
{
this.name = name;
this.value = value;
}
public override string Name { get { return name; } }
public override string Namespace { get { return "http://security.amazonaws.com/doc/2007-01-01/"; } }
protected override void OnWriteHeaderContents(XmlDictionaryWriter xmlDictionaryWriter, MessageVersion messageVersion)
{
xmlDictionaryWriter.WriteString(value);
}
}
}
Yes, they made it complicated when they started requiring message signing...
A simple and easy library is available on nuget.
PM> Install-Package Nager.AmazonProductAdvertising
Example
var authentication = new AmazonAuthentication("accesskey", "secretkey");
var client = new AmazonProductAdvertisingClient(authentication, AmazonEndpoint.US);
var result = await client.GetItemsAsync("B00BYPW00I");
To perform a lookup for anything other then an ASIN, you need to specify the "SearchIndex" property. You can simply set it to "All".
var request = new ItemLookupRequest();
request.ItemId = new[] {upcCode};
request.IdType = ItemLookupRequestIdType.UPC;
request.IdTypeSpecified = true;
request.SearchIndex = "All";
Here is a link to the documentation: http://docs.amazonwebservices.com/AWSECommerceService/2011-08-01/DG/index.html?ItemLookup.html. Note the description of the SearchIndex parameter:
Constraint:If ItemIdis an ASIN, a search index cannot be specified in
the request. Required for non-ASIN ItemIds.
I actually built a little wrapper around it so it hands you back a handy object graph. I have the source up on BitBucket and a little more about it on the C# Amazon ItemLookup page.
C# Amazon ItemLookup
You can make calls like:
var item = client.LookupByAsin("B0037X9N5U");
double? price = item.GetLowestPrice();