Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support new Basic authentication as signature is now deprecated #1

Open
keyo opened this issue Dec 18, 2016 · 0 comments
Open

Support new Basic authentication as signature is now deprecated #1

keyo opened this issue Dec 18, 2016 · 0 comments

Comments

@keyo
Copy link

keyo commented Dec 18, 2016

This might help someone. I had some problems with signatures not validating correctly. MixPanel documentation now says the signature way of authenticating is deprecated. So I've made changes to make the code work again.

I've pasted my changes to MixPanelDataClient.cs, MixPanelEndpoint.cs and MixPanelClientWrapper.cs below which include:

  • Added Segmentation Method for my own benefit
  • Fixed URL encoding of query string parameters
  • Replace old HTTP + signature authentication code with HTTPS + Basic auth

I don't have the time or will to commit this and create a pull request but maybe someone else does.

public class MixpanelDataClient : IMixpanelData {

	private readonly IHttpClient httpClient;

	public string ApiKey { get; private set; }
	public string ApiSecret { get; private set; }
	public string Token { get; private set; }

	public MixpanelDataClient(string apiKey, string apiSecret, string apiToken) {
		ApiKey = apiKey;
		ApiSecret = apiSecret;
		Token = apiToken;

		httpClient = new HttpClientWrapper(apiSecret);
	}

	public async Task<Stream> ExportStream(DateTime from, DateTime to, ICollection<string> events = null, string where = "", string bucket = "") {
		var parameters = new NameValueCollection();
		parameters.AddIfNotIsNullOrWhiteSpace("from_date", from.ToString("yyyy-MM-dd"));
		parameters.AddIfNotIsNullOrWhiteSpace("to_date", to.ToString("yyyy-MM-dd"));
		parameters.AddIfNotIsNullOrWhiteSpace("where", where);

		var endpoint = new Endpoint().Create()
									 .ForMethod(MethodEnum.Export)
									 .WithParameters(parameters)
									 .Build();

		this.httpClient.TimeOut = TimeSpan.FromHours(1);

		var response = await this.httpClient.GetAsync(endpoint, HttpCompletionOption.ResponseHeadersRead);

		if(response.IsSuccessStatusCode) {
			return await response.Content.ReadAsStreamAsync();
		}

		return null;
	}

	public async Task<ExportResponse> Export(DateTime from, DateTime to, ICollection<string> events = null, string where = "", string bucket = "") {
		var stream = await this.ExportStream(from, to, events, where, bucket);

		var reader = new StreamReader(stream);

		var exportResponse = new ExportResponse();

		while(reader.Peek() > -1) {
			exportResponse.Add(JsonConvert.DeserializeObject<Event>(reader.ReadLine()));
		}

		return exportResponse;
	}

	public async Task<EngageResponse> Engage(string where = "", string sessionId = "", int page = 0) {
		var parameters = new NameValueCollection();
		parameters.AddIfNotIsNullOrWhiteSpace("where", where);
		parameters.AddIfNotIsNullOrWhiteSpace("session_id", sessionId);
		parameters.AddIfNotIsNullOrWhiteSpace("page", page.ToString());

		var endpoint = new Endpoint().Create()
									 .ForMethod(MethodEnum.Engage)
									 .WithParameters(parameters)
									 .Build();
		var response = await this.httpClient.GetAsync(endpoint);

		if(response.IsSuccessStatusCode) {
			return await response.Content.ReadAsAsync<EngageResponse>();
		}

		return null;
	}

	public async Task<SegmentationResponse> Segmentation(string eventName, DateTime from, DateTime to, string on = null, string where = null) {
		var parameters = new NameValueCollection();
		parameters.AddIfNotIsNullOrWhiteSpace("event", eventName);
		parameters.AddIfNotIsNullOrWhiteSpace("from_date", from.ToString("yyyy-MM-dd"));
		parameters.AddIfNotIsNullOrWhiteSpace("to_date", to.ToString("yyyy-MM-dd"));
		parameters.AddIfNotIsNullOrWhiteSpace("on", on);
		parameters.AddIfNotIsNullOrWhiteSpace("where", where);

		var endpoint = new Endpoint().Create()
									 .ForMethod(MethodEnum.Segmentation)
									 .WithParameters(parameters)
									 .Build();

		var response = await this.httpClient.GetAsync(endpoint);

		if(response.IsSuccessStatusCode) {
			return await response.Content.ReadAsAsync<SegmentationResponse>();
		}

		string body = await response.Content.ReadAsStringAsync();

		throw new ApplicationException($"MixPanel API returned HTTP status:{response.StatusCode} response: {body}");
	}
}

public class HttpClientWrapper : IHttpClient {
	private readonly HttpClient httpClient;

	public HttpClientWrapper(string apiSecret) {
		httpClient = new HttpClient();
		var byteArray = Encoding.ASCII.GetBytes($"{apiSecret}:");
		httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
	}

	public async Task<HttpResponseMessage> GetAsync(string requestUri) {
		return await httpClient.GetAsync(requestUri);
	}

	public async Task<HttpResponseMessage> GetAsync(string requestUri, HttpCompletionOption httpCompletionOption) {
		return await httpClient.GetAsync(requestUri, httpCompletionOption);
	}

	public async Task<HttpResponseMessage> PostAsync(string requestUri, HttpContent content) {
		return await httpClient.PostAsync(requestUri, content);
	}

	public TimeSpan TimeOut {
		get { return httpClient.Timeout; }
		set { httpClient.Timeout = value; }
	}
}

public class Endpoint {
	public string BaseEndpoint { get; private set; }
	public string Method { get; private set; }
	public NameValueCollection Parameters { get; private set; }


	public Endpoint Create() {
		return this;
	}

	public Endpoint ForMethod(MethodEnum method) {
		this.Method = method.ToDescriptionString();
		this.BaseEndpoint = "https://mixpanel.com/api/2.0/";

		if(method == MethodEnum.Export) {
			this.BaseEndpoint = "https://data.mixpanel.com/api/2.0/";
		}

		return this;
	}

	public Endpoint WithParameters(NameValueCollection parameters) {
		this.Parameters = parameters;
		return this;
	}

	public string Build() {
		return this.ToString();
	}

	public override string ToString() {
		var uriBuilder = new UriBuilder($"{BaseEndpoint}/{Method}"); ;
		uriBuilder.Query = ToQueryString(this.Parameters);
		var endpoint = uriBuilder.ToString();
		return endpoint;
	}

	private string ToQueryString(NameValueCollection nvc) {
		var array = (from key in nvc.AllKeys
					 from value in nvc.GetValues(key)
					 select string.Format("{0}={1}", WebUtility.UrlEncode(key), WebUtility.UrlEncode(value)))
			.ToArray();
		return string.Join("&", array);
	}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant