Skip to content

Instantly share code, notes, and snippets.

@bojanrajkovic
Created April 22, 2014 16:10
Show Gist options
  • Save bojanrajkovic/11185047 to your computer and use it in GitHub Desktop.
Save bojanrajkovic/11185047 to your computer and use it in GitHub Desktop.
<%@ WebHandler Language="c#" Class="Xamarin.Activation.Studio" %>
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Web;
using System.Net;
using System.Text;
using MySql.Data.MySqlClient;
using System.Configuration;
using System.Collections.Generic;
using System.Collections.Specialized;
using Newtonsoft.Json;
using Mono.Touch.Activation.Server;
using Mono.Touch.Activation.Common;
using CustomerPortal;
using Xamarin.SSO.Client;
using System.Security.Cryptography;
using Segmentio;
using Segmentio.Model;
namespace Xamarin.Activation
{
public class Studio : IHttpHandler
{
const string Passphrase = "22BA3F38-7552-47BC-9548-ECA8AFB9356D";
const int IterationCount = 2000;
const int KeyLengthBits = 256; // Bits
const int SaltLength = 8; // Bytes
static readonly Client Analytics = new Client (ConfigurationManager.AppSettings["SegmentIoApiKey"] ?? "63thby1wjn4g5yfsvbjy");
static string DecryptString (string cipherString)
{
var input = Convert.FromBase64String (cipherString);
// First 16 bytes are the IV, then the next 8 are the salt, then the rest is the ciphertext.
byte[] salt = new byte[SaltLength], iv = new byte[16], ciphertext = new byte[input.Length - 24];
Buffer.BlockCopy (input, 0, iv, 0, 16);
Buffer.BlockCopy (input, 16, salt, 0, 8);
Buffer.BlockCopy (input, 24, ciphertext, 0, input.Length - 24);
byte[] key = DeriveKey (salt);
byte[] plainBytes = null;
using (var aes = new AesCryptoServiceProvider ()) {
using (var ms = new MemoryStream ()) {
var transform = aes.CreateDecryptor (key, iv);
using (var cs = new CryptoStream (ms, transform, CryptoStreamMode.Write))
cs.Write (ciphertext, 0, ciphertext.Length);
plainBytes = ms.ToArray ();
}
}
return Encoding.UTF8.GetString (plainBytes);
}
static byte[] DeriveKey (byte[] salt)
{
var kdf = new Rfc2898DeriveBytes (Passphrase, salt, IterationCount);
return kdf.GetBytes (KeyLengthBits / 8);
}
public bool IsReusable {
get { return true; }
}
public void ProcessRequest (HttpContext context)
{
var request = context.Request;
var response = context.Response;
Activate (request, response);
}
private static string GetRequestBody (HttpRequest Request)
{
string body;
using (Stream receiveStream = Request.InputStream) {
using (StreamReader readStream = new StreamReader (receiveStream, Encoding.UTF8)) {
body = readStream.ReadToEnd();
}
}
return body;
}
static readonly int[] ProductIdsThatTrumpTrials = new [] { 1, 12, 19, 20, 23, 26 };
static void Activate (HttpRequest req, HttpResponse resp)
{
resp.ContentType = "application/json";
var actualData = GetRequestBody (req);
var token = req.QueryString["token"];
var guid = req.QueryString["guid"];
var product = req.QueryString["product"];
var db = new DB ();
resp.StatusCode = 200;
if (string.IsNullOrWhiteSpace (guid) || string.IsNullOrWhiteSpace (token) || string.IsNullOrWhiteSpace (product) || string.IsNullOrWhiteSpace (actualData)) {
// TODO: Figure out what to send to analytics here
resp.Write (JsonConvert.SerializeObject (new { code = -1, message = "Missing data." }));
return;
}
Tuple<string, string> userCodes;
string file = DecryptString (actualData), name, email, company, phone, activation_code;
Xamarin.SSO.Client.User user = null;
// Try to get the user via direct DB access
try {
var localUser = db.GetUserInfo (token, guid);
if (localUser != null) {
user = new Xamarin.SSO.Client.User {
Guid = localUser.Guid,
Email = localUser.Email,
FirstName = localUser.FirstName,
LastName = localUser.LastName,
Company = localUser.Company,
PhoneNumber = localUser.PhoneNumber,
};
} else { user = null; }
} catch {
user = null;
}
var ssoApiUrl = ConfigurationManager.AppSettings["SSOApiUrl"];
var client = new XamarinSSOClient (ssoApiUrl, ConfigurationManager.AppSettings["SSOApiKey"]);
if (user == null) {
try {
Console.Error.WriteLine ("Looking up token {0}", token);
var tokenInfo = client.GetTokenInformation (token);
user = tokenInfo.User;
} catch (Exception e) {
resp.Write (JsonConvert.SerializeObject (new { code = -2, message = "Failed to look up user.", messageDetail = e.Message }));
return;
}
}
if (user == null) {
// TODO: Figure out what to send to analytics here
resp.Write (JsonConvert.SerializeObject (new { code = -2, message = "Could not find user with matching ID." }));
return;
}
if (user.Guid != guid) {
// TODO: Figure out what to send to analytics here
resp.Write (JsonConvert.SerializeObject (new { code = -2, message = "User doesn't match token." }));
return;
}
name = user.FirstName + " " + user.LastName;
email = user.Email;
company = user.Company;
phone = user.PhoneNumber;
// Now we need to look up activation codes for this user...
userCodes = db.GetActivationCodeForUser (product, guid);
if (string.IsNullOrWhiteSpace (userCodes.Item1) && string.IsNullOrWhiteSpace (userCodes.Item2)) {
// Try forcing auto-assignment from the store.
try {
var wc = new WebClient ();
var host = new Uri (ssoApiUrl).Host;
var storeDomain = host.Substring (host.IndexOf ('.'));
var storeName = storeDomain != ".xamarin.com" ? "taco" : "store";
var res = wc.DownloadString (string.Format ("https://{0}{1}/api/getlicensesforuser?guid={2}", storeName, storeDomain, user.Guid));
userCodes = db.GetActivationCodeForUser (product, guid);
// OK, auto-assignment should have happened, but it apparently didn't.
if (string.IsNullOrWhiteSpace (userCodes.Item1) && string.IsNullOrWhiteSpace (userCodes.Item2)) {
TrackError (user, -3, "Could not look up activation code.");
resp.Write (JsonConvert.SerializeObject (new { code = -3, message = "Could not look up activation code." }));
return;
}
} catch (Exception e) {
TrackError (user, -3, "Could not look up activation code.");
resp.Write (JsonConvert.SerializeObject (new { code = -3, message = "Could not look up activation code." }));
return;
}
}
var realInfo = userCodes.Item1 != null ? db.GetLicenseData (userCodes.Item1) : null;
var trialInfo = userCodes.Item2 != null ? db.GetLicenseData (userCodes.Item2) : null;
bool didReturnTrial;
if (realInfo == null && trialInfo == null) {
TrackError (user, -3, "Could not look up extended information for license.");
resp.Write (JsonConvert.SerializeObject (new { code = -3, message = "Could not look up extended information for license." }));
return;
}
if (trialInfo != null) {
// OK, they have a trial that's not disabled. Does it trump their license?
if (realInfo == null) {
// Real info is null, return the trial.
activation_code = userCodes.Item2;
didReturnTrial = true;
} else {
// Does the real license trump the trial?
int realProductId = realInfo.ProductId;
if (ProductIdsThatTrumpTrials.Contains (realProductId) || (trialInfo.ValidUntil > new DateTime (2001, 1, 1, 0, 0, 0) && trialInfo.ValidUntil < DateTime.UtcNow)) {
didReturnTrial = false;
activation_code = userCodes.Item1;
} else {
didReturnTrial = true;
activation_code = userCodes.Item2;
}
}
} else {
didReturnTrial = false;
activation_code = userCodes.Item1;
}
var data = new UserData () {
Name = name,
Email = email,
Company = company ?? String.Empty,
Phone = phone ?? String.Empty,
ActivationCode = activation_code,
ProductVersion = -1,
DataFile = file
};
var ip = ServerUtil.GetClientIP (req.UserHostAddress, req.Headers["X-Forwarded-For"]);
var service = new ActivationService ();
var response = service.ApiActivate (product, data, ip);
var success = response.ResponseCode == ActivationResponseCode.Success;
int usedSlots = 0, availableSlots = 0;
db.GetActivationSlotCountsForUser (guid, ref usedSlots, ref availableSlots);
if (!success) {
DbUtils.Log ("error", "activation", ip, "Didn't activate anything");
const string msg_already_in_use = "This registration code is already in use. " +
"If you need to transfer this license to another machine, " +
"please visit https://store.xamarin.com/account/products";
if (response.Message.Equals (msg_already_in_use, StringComparison.OrdinalIgnoreCase)) {
TrackError (user, -4, "You have reached the maximum number of activations.");
resp.Write (JsonConvert.SerializeObject (new { code = -4, message = "You have reached the maximum number of activations.", usedMachines = usedSlots, allowedMachines = availableSlots }));
return;
}
TrackError (user, (int) response.ResponseCode, response.Message);
resp.Write (JsonConvert.SerializeObject (new { code = response.ResponseCode, message = response.Message, messageDetail = response.Description, license = (string) null }));
return;
}
var info = didReturnTrial ? trialInfo : realInfo;
int product_id = info.ProductId;
string valid_until = info.ValidUntil.ToString ("u");
string level = GetProductLevelString (product_id);
var couldDowngradeTo = realInfo != null && didReturnTrial ? GetProductLevelString (realInfo.ProductId) : null;
Analytics.Identify (user.Email, new Traits {
{"firstName", user.FirstName},
{"lastName", user.LastName},
{"email", user.Email},
{"Last Successful License Sync", DateTime.UtcNow},
{"Workstations Used For Activation", usedSlots}
});
Analytics.Track (user.Email, "Synced License Successfully", new Properties {
{"Resulting License Is Trial", didReturnTrial},
{"Resulting License Level", level},
{"Resulting License Expiration Time", valid_until},
{"License Product", product}
});
resp.Write (JsonConvert.SerializeObject (new {
code = response.ResponseCode, license = response.License, message = response.Message,
messageDetail = response.Description, level, validUntil = valid_until,
usedMachines = usedSlots, allowedMachines = availableSlots,
nonTrialLevel = couldDowngradeTo, nonTrialValidUntil = didReturnTrial && realInfo != null ? realInfo.ValidUntil.ToString ("u") : null,
// if we have trial info, and the trial is expired (valid until <= now), and the trial is not disabled, return true
hasHadTrial = trialInfo != null && trialInfo.ValidUntil <= DateTime.UtcNow && trialInfo.Enabled
}));
}
static void TrackError (User user, int errorCode, string message)
{
Analytics.Identify (user.Email, new Traits {
{"firstName", user.FirstName},
{"lastName", user.LastName},
{"email", user.Email},
{"Last Activation Error Timestamp", DateTime.UtcNow},
{"Last Activation Error", message}
});
Analytics.Track (user.Email, "Got Activation Error", new Properties {
{"Error Message", message},
{"Error Code", errorCode}
});
}
static string GetProductLevelString (int productId)
{
switch (((ProductId) productId)) {
case ProductId.None:
return "None";
case ProductId.MonoTouchIndie:
case ProductId.MAIndie:
case ProductId.XamarinMacIndie:
return "Indie";
case ProductId.MonoTouchProfessional:
case ProductId.MAProfessional:
case ProductId.XamarinMacBusiness:
case ProductId.MonoTouchPromotional:
case ProductId.MonoTouchAcademic:
case ProductId.MAPromotional:
case ProductId.MAAcademic:
case ProductId.MonoTouchEnterprise:
case ProductId.MonoTouchEnterprise5:
case ProductId.MAEnterprise:
case ProductId.XamarinMacEnterprise:
case ProductId.MonoTouchStandard:
case ProductId.MAStandard:
case ProductId.XamarinMacProfessional:
case ProductId.MAEnterprise5:
return "Business";
case ProductId.MonoTouchEnterprisePrio:
case ProductId.MAEnterprisePrio:
case ProductId.MonoTouchPriority:
case ProductId.MAPriority:
case ProductId.XamarinMacPriority:
case ProductId.XamarinMacEnterprisePrio:
return "Priority";
case ProductId.MonoTouchTrial:
case ProductId.MATrial:
return "Trial";
default:
return "None";
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment