diff --git a/Fitbit/Fitbit-WithSamples.sln b/Fitbit/Fitbit-WithSamples.sln index 679b14a1..2ad90244 100644 --- a/Fitbit/Fitbit-WithSamples.sln +++ b/Fitbit/Fitbit-WithSamples.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{C284DD15-3355-4C6A-9EE0-D0A3BEA4D8A5}" ProjectSection(SolutionItems) = preProject @@ -21,6 +21,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebMVC.Portable", ".. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleWebMVCOAuth2", "..\SampleWebMVCOAuth2\SampleWebMVCOAuth2.csproj", "{61C8AD29-A128-442E-BF0B-539A990B15C7}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleConsole", "..\SampleConsole\SampleConsole\SampleConsole.csproj", "{11886EF6-6527-4061-A859-3191F7E3B192}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -58,6 +60,12 @@ Global {61C8AD29-A128-442E-BF0B-539A990B15C7}.Release|Any CPU.Build.0 = Release|Any CPU {61C8AD29-A128-442E-BF0B-539A990B15C7}.TrialRelease|Any CPU.ActiveCfg = Release|Any CPU {61C8AD29-A128-442E-BF0B-539A990B15C7}.TrialRelease|Any CPU.Build.0 = Release|Any CPU + {11886EF6-6527-4061-A859-3191F7E3B192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {11886EF6-6527-4061-A859-3191F7E3B192}.Debug|Any CPU.Build.0 = Debug|Any CPU + {11886EF6-6527-4061-A859-3191F7E3B192}.Release|Any CPU.ActiveCfg = Release|Any CPU + {11886EF6-6527-4061-A859-3191F7E3B192}.Release|Any CPU.Build.0 = Release|Any CPU + {11886EF6-6527-4061-A859-3191F7E3B192}.TrialRelease|Any CPU.ActiveCfg = Release|Any CPU + {11886EF6-6527-4061-A859-3191F7E3B192}.TrialRelease|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SampleConsole/SampleConsole/App.config b/SampleConsole/SampleConsole/App.config new file mode 100644 index 00000000..13447893 --- /dev/null +++ b/SampleConsole/SampleConsole/App.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/SampleConsole/SampleConsole/AuthorizationHelper.cs b/SampleConsole/SampleConsole/AuthorizationHelper.cs new file mode 100644 index 00000000..0803490f --- /dev/null +++ b/SampleConsole/SampleConsole/AuthorizationHelper.cs @@ -0,0 +1,143 @@ +using Fitbit.Api.Portable; +using Fitbit.Api.Portable.OAuth2; +using OutputColorizer; +using System; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace SampleConsole +{ + public static class AuthorizationHelper + { + public static FitbitClient GetAuthorizedFitBitClient(params string[] scopes) + { + // try to retrieve the token from disk + OAuth2AccessToken token = GetAccessTokenAsync(scopes).Result; + + return new FitbitClient(new FitbitAppCredentials() { ClientId = Options.ClientId, ClientSecret = Options.ClientSecret }, token, true); + } + + public static async Task GetAccessTokenAsync(params string[] scopes) + { + var token = ReadTokenFromDisk(); + if (token == null || token.UtcExpirationDate < DateTime.Now.ToUniversalTime()) + { + // we need admin to retrieve the token automatically + RestartAsElevatedIfNeeded(); + + token = await AuthorizeToFitBitAsync(scopes.Length == 0 ? Options.AllScopes : scopes); + + SaveTokenToFile(token); + } + + return token; + } + + private static void SaveTokenToFile(OAuth2AccessToken token) + { + try + { + XmlSerializer ser = new XmlSerializer(typeof(OAuth2AccessToken)); + using (StreamWriter sw = new StreamWriter("token.dat")) + { + ser.Serialize(sw, token); + } + } + catch + { + } + } + + private static OAuth2AccessToken ReadTokenFromDisk() + { + if (!File.Exists("token.dat")) + { + return null; + } + + try + { + XmlSerializer ser = new XmlSerializer(typeof(OAuth2AccessToken)); + using (StreamReader sr = new StreamReader("token.dat")) + { + return ser.Deserialize(sr) as OAuth2AccessToken; + } + } + catch + { + return null; + } + } + + private static async Task AuthorizeToFitBitAsync(string[] scopes) + { + Colorizer.WriteLine("Sending authorization request..."); + string scope = string.Join("%20", scopes); + string authorizeUrl = $"https://www.fitbit.com/oauth2/authorize?response_type=code&client_id={Options.ClientId}&scope={scope}&expires_in=86400"; + Process.Start(authorizeUrl); + + Colorizer.WriteLine("Waiting for callback at [Yellow!http://localhost]"); + string code; + using (HttpListener listener = new HttpListener()) + { + listener.Prefixes.Add("http://localhost/"); + listener.Start(); + HttpListenerContext context = listener.GetContext(); + + Colorizer.WriteLine("Request received, retrieving code"); + HttpListenerRequest request = context.Request; + + //retrieve the code from the raw request. + code = request.QueryString["code"]; + } + + Colorizer.WriteLine("Exchanging code for authentication token"); + using (HttpClient hc = new HttpClient()) + { + HttpRequestMessage requestMsg = new HttpRequestMessage(); + requestMsg.Method = HttpMethod.Post; + requestMsg.RequestUri = new Uri("https://api.fitbit.com/oauth2/token"); + string authorizationHeader = Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes($"{Options.ClientId}:{Options.ClientSecret}")); + requestMsg.Headers.Add("Authorization", "Basic " + authorizationHeader); + requestMsg.Content = new StringContent($"client_id={Options.ClientSecret}&grant_type=authorization_code&code={code}", Encoding.ASCII, "application/x-www-form-urlencoded"); + + Colorizer.Write("Making request..."); + using (var responseMsg = await hc.SendAsync(requestMsg)) + { + Colorizer.WriteLine("[Green!done]."); + if (responseMsg.IsSuccessStatusCode) + { + var tok = OAuth2Helper.ParseAccessTokenResponse(await responseMsg.Content.ReadAsStringAsync()); + tok.UtcExpirationDate = DateTime.Now.ToUniversalTime().AddSeconds(tok.ExpiresIn); + + return tok; + } + } + } + + return null; + } + + private static void RestartAsElevatedIfNeeded() + { + // we can't authorize if we are not admin because we require access to register a listener to http://localhost. + if (!WindowsIdentity.GetCurrent().Owner.IsWellKnown(WellKnownSidType.BuiltinAdministratorsSid)) + { + // start the same process (as admin) + Process elevatedProcess = new Process(); + elevatedProcess.StartInfo.FileName = System.Reflection.Assembly.GetEntryAssembly().Location; + elevatedProcess.StartInfo.Arguments = string.Join(" ", Environment.GetCommandLineArgs()); // pass whatever arguments were passed before. + elevatedProcess.StartInfo.Verb = "runas"; //run as admin + elevatedProcess.Start(); + + Environment.Exit(0); + } + } + } +} diff --git a/SampleConsole/SampleConsole/Options.cs b/SampleConsole/SampleConsole/Options.cs new file mode 100644 index 00000000..2c98cb3a --- /dev/null +++ b/SampleConsole/SampleConsole/Options.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SampleConsole +{ + public class Options + { + public static string ClientId = System.Configuration.ConfigurationManager.AppSettings["FitbitConsumerKey"]; + public static string ClientSecret = System.Configuration.ConfigurationManager.AppSettings["FitbitConsumerSecret"]; + public static string[] AllScopes = new string[] { "activity ", "nutrition ", "heartrate ", "location ", "nutrition ", "profile ", "settings ", "sleep ", "social ", "weight" }; + } +} diff --git a/SampleConsole/SampleConsole/Program.cs b/SampleConsole/SampleConsole/Program.cs new file mode 100644 index 00000000..a7fac9c9 --- /dev/null +++ b/SampleConsole/SampleConsole/Program.cs @@ -0,0 +1,31 @@ +using Fitbit.Api.Portable; +using Fitbit.Models; +using OutputColorizer; +using System; +using System.Collections.Generic; +using System.IO; + +namespace SampleConsole +{ + class Program + { + static void Main(string[] args) + { + // Authorize with FitBit + FitbitClient fc = AuthorizationHelper.GetAuthorizedFitBitClient("activity ", "nutrition ", "heartrate ", "location ", "nutrition ", "profile ", "settings ", "sleep ", "social ", "weight"); + + // Retrieve the weight information for today + DateTime start = DateTime.Now; + Colorizer.Write("Processing [Yellow!{0}]...", start.ToShortDateString()); + var weight = fc.GetWeightAsync(start, DateRangePeriod.OneMonth).Result; + Colorizer.WriteLine("found [Green!{0}] entries.", weight.Weights.Count); + + // Save the downloaded information to disk + System.Xml.Serialization.XmlSerializer src = new System.Xml.Serialization.XmlSerializer(typeof(List)); + using (StreamWriter sw = new StreamWriter("weight.txt")) + { + src.Serialize(sw, weight); + } + } + } +} diff --git a/SampleConsole/SampleConsole/Properties/AssemblyInfo.cs b/SampleConsole/SampleConsole/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..aa390a5d --- /dev/null +++ b/SampleConsole/SampleConsole/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SampleConsole")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SampleConsole")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("11886ef6-6527-4061-a859-3191f7e3b192")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SampleConsole/SampleConsole/SampleConsole.csproj b/SampleConsole/SampleConsole/SampleConsole.csproj new file mode 100644 index 00000000..ae03731f --- /dev/null +++ b/SampleConsole/SampleConsole/SampleConsole.csproj @@ -0,0 +1,74 @@ + + + + + Debug + AnyCPU + {11886EF6-6527-4061-A859-3191F7E3B192} + Exe + Properties + SampleConsole + SampleConsole + v4.5.2 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\..\Fitbit\packages\OutputColorizer.1.1.0\lib\net45\OutputColorizer.dll + True + + + + + + + + + + + + + + + + + + + + + + + + {1358d3b4-0698-4003-97eb-b6d489e04138} + Fitbit.Portable + + + + + \ No newline at end of file diff --git a/SampleConsole/SampleConsole/packages.config b/SampleConsole/SampleConsole/packages.config new file mode 100644 index 00000000..9156353e --- /dev/null +++ b/SampleConsole/SampleConsole/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file