From 2f88f065b5d2002c7eb73691538548e7149c43fa Mon Sep 17 00:00:00 2001 From: Wiktor Lawski Date: Mon, 19 Oct 2020 15:29:46 +0200 Subject: [PATCH 1/3] Add console sample app offering C++ interface In case of native apps, list of libraries and samples is limited to Android, iOS & Windows (this repository): https://developers.google.com/identity/protocols/oauth2/native-app#libraries Google APIs Client Library for C++ exists but it is deprecated as of May 30th 2019. To be able to develop new projects requiring C++ without usage of deprecated library and without the need of maintaining your own custom solution, you can rely on available client libraries listed here: https://developers.google.com/identity/protocols/oauth2#libraries and prepare just a thin wrapper. Core logic remains mostly unchanged in comparison to pure C# based console (CLI) sample application. To use it in standard C++, thin layer of CLR is added in-between. --- OAuthConsoleAppCpp/CLRLib/AssemblyInfo.cpp | 36 +++ OAuthConsoleAppCpp/CLRLib/CLRLib.cpp | 78 +++++ OAuthConsoleAppCpp/CLRLib/CLRLib.h | 37 +++ OAuthConsoleAppCpp/CLRLib/CLRLib.vcxproj | 158 ++++++++++ .../CLRLib/CLRLib.vcxproj.filters | 46 +++ OAuthConsoleAppCpp/CLRLib/ReadMe.txt | 38 +++ OAuthConsoleAppCpp/CLRLib/app.ico | Bin 0 -> 41395 bytes OAuthConsoleAppCpp/CLRLib/app.rc | Bin 0 -> 2572 bytes OAuthConsoleAppCpp/CLRLib/resource.h | 3 + OAuthConsoleAppCpp/CSharpLib/CSharpLib.cs | 287 ++++++++++++++++++ OAuthConsoleAppCpp/CSharpLib/CSharpLib.csproj | 58 ++++ .../CSharpLib/Properties/AssemblyInfo.cs | 36 +++ OAuthConsoleAppCpp/CSharpLib/packages.config | 4 + OAuthConsoleAppCpp/OAuthConsoleAppCpp.sln | 69 +++++ .../OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp | 41 +++ .../OAuthConsoleAppCpp.vcxproj | 168 ++++++++++ .../OAuthConsoleAppCpp.vcxproj.filters | 30 ++ .../OAuthConsoleAppCpp/ReadMe.txt | 40 +++ .../OAuthConsoleAppCpp/targetver.h | 8 + OAuthConsoleAppCpp/README.md | 92 ++++++ README.md | 5 +- 21 files changed, 1233 insertions(+), 1 deletion(-) create mode 100644 OAuthConsoleAppCpp/CLRLib/AssemblyInfo.cpp create mode 100644 OAuthConsoleAppCpp/CLRLib/CLRLib.cpp create mode 100644 OAuthConsoleAppCpp/CLRLib/CLRLib.h create mode 100644 OAuthConsoleAppCpp/CLRLib/CLRLib.vcxproj create mode 100644 OAuthConsoleAppCpp/CLRLib/CLRLib.vcxproj.filters create mode 100644 OAuthConsoleAppCpp/CLRLib/ReadMe.txt create mode 100644 OAuthConsoleAppCpp/CLRLib/app.ico create mode 100644 OAuthConsoleAppCpp/CLRLib/app.rc create mode 100644 OAuthConsoleAppCpp/CLRLib/resource.h create mode 100644 OAuthConsoleAppCpp/CSharpLib/CSharpLib.cs create mode 100644 OAuthConsoleAppCpp/CSharpLib/CSharpLib.csproj create mode 100644 OAuthConsoleAppCpp/CSharpLib/Properties/AssemblyInfo.cs create mode 100644 OAuthConsoleAppCpp/CSharpLib/packages.config create mode 100644 OAuthConsoleAppCpp/OAuthConsoleAppCpp.sln create mode 100644 OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp create mode 100644 OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.vcxproj create mode 100644 OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.vcxproj.filters create mode 100644 OAuthConsoleAppCpp/OAuthConsoleAppCpp/ReadMe.txt create mode 100644 OAuthConsoleAppCpp/OAuthConsoleAppCpp/targetver.h create mode 100644 OAuthConsoleAppCpp/README.md diff --git a/OAuthConsoleAppCpp/CLRLib/AssemblyInfo.cpp b/OAuthConsoleAppCpp/CLRLib/AssemblyInfo.cpp new file mode 100644 index 0000000..04c7af9 --- /dev/null +++ b/OAuthConsoleAppCpp/CLRLib/AssemblyInfo.cpp @@ -0,0 +1,36 @@ +using namespace System; +using namespace System::Reflection; +using namespace System::Runtime::CompilerServices; +using namespace System::Runtime::InteropServices; +using namespace System::Security::Permissions; + +// +// 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:AssemblyTitleAttribute(L"CLRLib")]; +[assembly:AssemblyDescriptionAttribute(L"")]; +[assembly:AssemblyConfigurationAttribute(L"")]; +[assembly:AssemblyCompanyAttribute(L"")]; +[assembly:AssemblyProductAttribute(L"CLRLib")]; +[assembly:AssemblyCopyrightAttribute(L"Copyright (c) 2020")]; +[assembly:AssemblyTrademarkAttribute(L"")]; +[assembly:AssemblyCultureAttribute(L"")]; + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the value or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly:AssemblyVersionAttribute("1.0.*")]; + +[assembly:ComVisible(false)]; + +[assembly:CLSCompliantAttribute(true)]; \ No newline at end of file diff --git a/OAuthConsoleAppCpp/CLRLib/CLRLib.cpp b/OAuthConsoleAppCpp/CLRLib/CLRLib.cpp new file mode 100644 index 0000000..d130680 --- /dev/null +++ b/OAuthConsoleAppCpp/CLRLib/CLRLib.cpp @@ -0,0 +1,78 @@ +// Copyright 2020 Wiktor Lawski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#using "CSharpLib.dll" + +#include + +#include "CLRLib.h" + +using System::String; +using System::Runtime::InteropServices::Marshal; + +using namespace System; +using namespace System::Runtime::InteropServices; + +namespace +{ + outputCallback currentCallback; + + void outputCallbackWrapper(String^ output) + { + auto outputPtr = Marshal::StringToHGlobalAnsi(output).ToPointer(); + + currentCallback(static_cast(outputPtr)); + + Marshal::FreeHGlobal(static_cast(outputPtr)); + } +} + +namespace OAuthConsoleAppCpp +{ + class CSharpLibWrapper + { + public: + msclr::auto_gcroot csharpLib; + }; + + CLRLib::CLRLib() + { + wrapper = new CSharpLibWrapper(); + wrapper->csharpLib = gcnew CSharpLib(); + + wrapper->csharpLib->output += gcnew CSharpLib::outputDelegate(outputCallbackWrapper); + } + + CLRLib::~CLRLib() + { + wrapper->csharpLib->output -= gcnew CSharpLib::outputDelegate(outputCallbackWrapper); + + delete wrapper; + } + + void CLRLib::printPrompt() + { + wrapper->csharpLib->printPrompt(); + } + + void CLRLib::performAuth(const char * clientID, const char * clientSecret) + { + wrapper->csharpLib->performAuth(gcnew String(clientID), gcnew String(clientSecret)); + } + + void CLRLib::setOutputCallback(outputCallback callback) + { + currentCallback = callback; + } +} \ No newline at end of file diff --git a/OAuthConsoleAppCpp/CLRLib/CLRLib.h b/OAuthConsoleAppCpp/CLRLib/CLRLib.h new file mode 100644 index 0000000..003e861 --- /dev/null +++ b/OAuthConsoleAppCpp/CLRLib/CLRLib.h @@ -0,0 +1,37 @@ +// Copyright 2020 Wiktor Lawski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +typedef void(__cdecl *outputCallback)(const char *); + +namespace OAuthConsoleAppCpp +{ + class CSharpLibWrapper; + + class _declspec(dllexport) CLRLib + { + public: + CLRLib(); + ~CLRLib(); + + void printPrompt(); + void performAuth(const char * clientID, const char * clientSecret); + + static void setOutputCallback(outputCallback callback); + + private: + CSharpLibWrapper* wrapper; + }; +} diff --git a/OAuthConsoleAppCpp/CLRLib/CLRLib.vcxproj b/OAuthConsoleAppCpp/CLRLib/CLRLib.vcxproj new file mode 100644 index 0000000..65754a2 --- /dev/null +++ b/OAuthConsoleAppCpp/CLRLib/CLRLib.vcxproj @@ -0,0 +1,158 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD} + v4.5.2 + ManagedCProj + CLRLib + 8.1 + + + + DynamicLibrary + true + v140 + true + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + DynamicLibrary + true + v140 + true + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Level3 + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + NotUsing + $(SolutionDir)\CSharpLib\bin\$(ConfigurationName);%(AdditionalUsingDirectories) + + + + $(SolutionDir)\CSharpLib\bin\$(ConfigurationName) + + + + + Level3 + Disabled + _DEBUG;%(PreprocessorDefinitions) + Use + + + + + + + + Level3 + WIN32;NDEBUG;%(PreprocessorDefinitions) + NotUsing + $(SolutionDir)\CSharpLib\bin\$(ConfigurationName);%(AdditionalUsingDirectories) + + + + $(SolutionDir)\CSharpLib\bin\$(ConfigurationName) + + + + + Level3 + NDEBUG;%(PreprocessorDefinitions) + Use + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OAuthConsoleAppCpp/CLRLib/CLRLib.vcxproj.filters b/OAuthConsoleAppCpp/CLRLib/CLRLib.vcxproj.filters new file mode 100644 index 0000000..0950f8e --- /dev/null +++ b/OAuthConsoleAppCpp/CLRLib/CLRLib.vcxproj.filters @@ -0,0 +1,46 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + + + + Resource Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/OAuthConsoleAppCpp/CLRLib/ReadMe.txt b/OAuthConsoleAppCpp/CLRLib/ReadMe.txt new file mode 100644 index 0000000..e65dc84 --- /dev/null +++ b/OAuthConsoleAppCpp/CLRLib/ReadMe.txt @@ -0,0 +1,38 @@ +======================================================================== + DYNAMIC LINK LIBRARY : CLRLib Project Overview +======================================================================== + +AppWizard has created this CLRLib DLL for you. + +This file contains a summary of what you will find in each of the files that +make up your CLRLib application. + +CLRLib.vcxproj + This is the main project file for VC++ projects generated using an Application Wizard. + It contains information about the version of Visual C++ that generated the file, and + information about the platforms, configurations, and project features selected with the + Application Wizard. + +CLRLib.vcxproj.filters + This is the filters file for VC++ projects generated using an Application Wizard. + It contains information about the association between the files in your project + and the filters. This association is used in the IDE to show grouping of files with + similar extensions under a specific node (for e.g. ".cpp" files are associated with the + "Source Files" filter). + +CLRLib.cpp + This is the main DLL source file. + +CLRLib.h + This file contains a class declaration. + +AssemblyInfo.cpp + Contains custom attributes for modifying assembly metadata. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" to indicate parts of the source code you +should add to or customize. + +///////////////////////////////////////////////////////////////////////////// diff --git a/OAuthConsoleAppCpp/CLRLib/app.ico b/OAuthConsoleAppCpp/CLRLib/app.ico new file mode 100644 index 0000000000000000000000000000000000000000..789d7ccbb56ed92f1bb005054ec7e67257ddc37e GIT binary patch literal 41395 zcmeHQZA=_R7=DjCkZTKK8e`L%_DrfaQ35q-NU0yk2`Okwv@x+7+O*QNL}I~`Y8$Mc zweceoTVpWz1KLD?5Tj6HjAC->RZTH5(3+Y^qR}Q*QIUg61#Btj%&kM`7H)6nj+0&P zne4DLH}A~6^UO0d@9xgBL=JKjcMi&?At%w>EL>Qq#oKdt>C$N+(Rua|At$Lyk0H7#>diEi}F0wek;+HU7|b|XXU)xB+Bzp4Xf+HR-)G) zs@#~fX!#;mY)(aa>1JM9q{b|Es@mJmiXcbB=8Zn;=)3uM7IEz^*;GQ*b!7464yF^i z>&qW&Ajxqo~ z`o$M|S6|Ksp*!JynDLQBBox`-#bTKXNNLW6@ zgkx${f)e9J7HU zW>BoNwY0UbHnH&K*Qp-nz06Nvr^+meah=M<;eY@TNCN~S6LgB(nf&r-v~=^*=c0n&Ojh75tz+RnK5HVPvXohh+3)UAs(fW-b|Sp*ELAr(aAhg;CWNC zNg-csQaH8gOTI~A>mQ?;HLt$xf4z2e-!~6*cy1{Dt+(p06<@t|uzZicMMS~oQ0<3D z0x!1grj?D(wFOt)E;k)Ed9+Tg3NEWIIN#a3e!Td!+uXBZlaG*-5o1m2`SF#ewPVG* z`nqqrRCRT#YNfyP4BvY&bah{6bzfHSM$0IhOhc`lceYNT^Phg?sL%8lI?Ns?&V`*c zRdBocy$a9gPIeW|Bs$T^;GR!b6_sshE;@YY_gp~UPvB*7Kl6N3QpB;1NN_*^2mk>f zpdrBaQ2Eo`hgtjKfdM$cIKcK+E@&TbtfT$t|Aou}RsOSm)?qt)eP5#e#_0u9G5^8-Gva`rFPQ&e|Jj)XdOjO*ANC*YKRsXU%zfB@ z$=iQ=KG;33I{(?dy?Xu8el`17kDuM+=zsLT5eMwf?|AwfwVkKM%WwB|v>)T&hy!-# zcRc-#+K%yWJT~HChjicv4%m(ZY5o2e-U9>zKp=e)5dSYJ-Z^~{vUkhl^=tHZCp?kH z`q>9+;D7)SxP}0;>rdL;uu&S+@th^rgWXn^{AFF=V`xmP6 zLQm8mZTksaRPj`m7xe&t;4gDP@IsYOs`8>9xo#d7zpIv~Dlh6m{oudM0l^DZKB>x! zdgQug{)_ZfpHctp{7*IRqI}>VjlUj`bctK)VsIXmk>B_FKdTq@^7|D0wuFay@!ess zi$&(x%>|#u+@X3Fb@P0QtXIgd%~cBlKUMBp!+F#H%p#;+^_JT@4G;wt`CM6edKVE&_NhVvfS%iLC zWSQHUGiT22J+r@mwbfS8nR1OZ*GvmqrX{p%XbY|MRufhydc+s>LGLxC&7m(D$I#}C zKDS~vVjfsoTCN*-TDq@$+|rI?6Y7-R3Ebnz+bErH>7yEv+b5fmFN^0a`$*4qp-Ww< ztG=G;jb5k+waffm1HFQBL4T%WCm? z#HcdTjDk5HGd^d`g1$jnf|mf_jFpk--9C5-`Wn%@f*SOW-uHcpJb|5?DBZ|6mh-4f zU@Wv65UF#Wg0&<19#Rg}(PPF=w4=jp-`A&J7>(t<#^&DE=APR)&#|vM|NF?hM(R`a z=x)7OKP=ylaS_o=w0=p|vK1#(C0DQB)(Z2v*`ND4?MZVSbLuoo@hbUue+?b#$)=7L zdTgo|Wn405~MQf{Gl(L2Owk}!eF@S>dpGZcAxx$556J7PH(B$0OeM$cQ YQ%QcTw@<1w+ApVHg7W`TIqtUg54YVV)&Kwi literal 0 HcmV?d00001 diff --git a/OAuthConsoleAppCpp/CLRLib/resource.h b/OAuthConsoleAppCpp/CLRLib/resource.h new file mode 100644 index 0000000..d5ac7c4 --- /dev/null +++ b/OAuthConsoleAppCpp/CLRLib/resource.h @@ -0,0 +1,3 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by app.rc diff --git a/OAuthConsoleAppCpp/CSharpLib/CSharpLib.cs b/OAuthConsoleAppCpp/CSharpLib/CSharpLib.cs new file mode 100644 index 0000000..12e3196 --- /dev/null +++ b/OAuthConsoleAppCpp/CSharpLib/CSharpLib.cs @@ -0,0 +1,287 @@ +// Copyright 2016 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Runtime.InteropServices; + +namespace OAuthConsoleAppCpp +{ + public class CSharpLib + { + public delegate void outputDelegate(string output); + + public event outputDelegate output; + + public void printPrompt() + { + output("+-----------------------+"); + output("| Sign in with Google |"); + output("+-----------------------+"); + output(""); + output("Press any key to sign in..."); + Console.ReadKey(); + } + + public void performAuth(string clientID, string clientSecret) + { + doOAuth(clientID, clientSecret); + } + + // client configuration + const string authorizationEndpoint = "https://accounts.google.com/o/oauth2/v2/auth"; + const string tokenEndpoint = "https://www.googleapis.com/oauth2/v4/token"; + const string userInfoEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo"; + + // ref http://stackoverflow.com/a/3978040 + public static int GetRandomUnusedPort() + { + var listener = new TcpListener(IPAddress.Loopback, 0); + listener.Start(); + var port = ((IPEndPoint)listener.LocalEndpoint).Port; + listener.Stop(); + return port; + } + + private async void doOAuth(string clientID, string clientSecret) + { + // Generates state and PKCE values. + string state = randomDataBase64url(32); + string code_verifier = randomDataBase64url(32); + string code_challenge = base64urlencodeNoPadding(sha256(code_verifier)); + const string code_challenge_method = "S256"; + + // Creates a redirect URI using an available port on the loopback address. + string redirectURI = string.Format("http://{0}:{1}/", IPAddress.Loopback, GetRandomUnusedPort()); + output("redirect URI: " + redirectURI); + + // Creates an HttpListener to listen for requests on that redirect URI. + var http = new HttpListener(); + http.Prefixes.Add(redirectURI); + output("Listening..."); + http.Start(); + + // Creates the OAuth 2.0 authorization request. + string authorizationRequest = string.Format("{0}?response_type=code&scope=openid%20profile&redirect_uri={1}&client_id={2}&state={3}&code_challenge={4}&code_challenge_method={5}", + authorizationEndpoint, + System.Uri.EscapeDataString(redirectURI), + clientID, + state, + code_challenge, + code_challenge_method); + + // Opens request in the browser. + System.Diagnostics.Process.Start(authorizationRequest); + + // Waits for the OAuth authorization response. + var context = await http.GetContextAsync(); + + // Brings the Console to Focus. + BringConsoleToFront(); + + // Sends an HTTP response to the browser. + var response = context.Response; + string responseString = string.Format("Please return to the app."); + var buffer = System.Text.Encoding.UTF8.GetBytes(responseString); + response.ContentLength64 = buffer.Length; + var responseOutput = response.OutputStream; + Task responseTask = responseOutput.WriteAsync(buffer, 0, buffer.Length).ContinueWith((task) => + { + responseOutput.Close(); + http.Stop(); + output("HTTP server stopped."); + }); + + // Checks for errors. + if (context.Request.QueryString.Get("error") != null) + { + output(String.Format("OAuth authorization error: {0}.", context.Request.QueryString.Get("error"))); + return; + } + if (context.Request.QueryString.Get("code") == null + || context.Request.QueryString.Get("state") == null) + { + output("Malformed authorization response. " + context.Request.QueryString); + return; + } + + // extracts the code + var code = context.Request.QueryString.Get("code"); + var incoming_state = context.Request.QueryString.Get("state"); + + // Compares the receieved state to the expected value, to ensure that + // this app made the request which resulted in authorization. + if (incoming_state != state) + { + output(String.Format("Received request with invalid state ({0})", incoming_state)); + return; + } + output("Authorization code: " + code); + + // Starts the code exchange at the Token Endpoint. + performCodeExchange(clientID, clientSecret, code, code_verifier, redirectURI); + } + + async void performCodeExchange(string clientID, string clientSecret, string code, string code_verifier, string redirectURI) + { + output("Exchanging code for tokens..."); + + // builds the request + string tokenRequestURI = "https://www.googleapis.com/oauth2/v4/token"; + string tokenRequestBody = string.Format("code={0}&redirect_uri={1}&client_id={2}&code_verifier={3}&client_secret={4}&scope=&grant_type=authorization_code", + code, + System.Uri.EscapeDataString(redirectURI), + clientID, + code_verifier, + clientSecret + ); + + // sends the request + HttpWebRequest tokenRequest = (HttpWebRequest)WebRequest.Create(tokenRequestURI); + tokenRequest.Method = "POST"; + tokenRequest.ContentType = "application/x-www-form-urlencoded"; + tokenRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; + byte[] _byteVersion = Encoding.ASCII.GetBytes(tokenRequestBody); + tokenRequest.ContentLength = _byteVersion.Length; + Stream stream = tokenRequest.GetRequestStream(); + await stream.WriteAsync(_byteVersion, 0, _byteVersion.Length); + stream.Close(); + + try + { + // gets the response + WebResponse tokenResponse = await tokenRequest.GetResponseAsync(); + using (StreamReader reader = new StreamReader(tokenResponse.GetResponseStream())) + { + // reads response body + string responseText = await reader.ReadToEndAsync(); + output(responseText); + + // converts to dictionary + Dictionary tokenEndpointDecoded = JsonConvert.DeserializeObject>(responseText); + + string access_token = tokenEndpointDecoded["access_token"]; + userinfoCall(access_token); + } + } + catch (WebException ex) + { + if (ex.Status == WebExceptionStatus.ProtocolError) + { + var response = ex.Response as HttpWebResponse; + if (response != null) + { + output("HTTP: " + response.StatusCode); + using (StreamReader reader = new StreamReader(response.GetResponseStream())) + { + // reads response body + string responseText = await reader.ReadToEndAsync(); + output(responseText); + } + } + + } + } + } + + async void userinfoCall(string access_token) + { + output("Making API Call to Userinfo..."); + + // builds the request + string userinfoRequestURI = "https://www.googleapis.com/oauth2/v3/userinfo"; + + // sends the request + HttpWebRequest userinfoRequest = (HttpWebRequest)WebRequest.Create(userinfoRequestURI); + userinfoRequest.Method = "GET"; + userinfoRequest.Headers.Add(string.Format("Authorization: Bearer {0}", access_token)); + userinfoRequest.ContentType = "application/x-www-form-urlencoded"; + userinfoRequest.Accept = "Accept=text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"; + + // gets the response + WebResponse userinfoResponse = await userinfoRequest.GetResponseAsync(); + using (StreamReader userinfoResponseReader = new StreamReader(userinfoResponse.GetResponseStream())) + { + // reads response body + string userinfoResponseText = await userinfoResponseReader.ReadToEndAsync(); + output(userinfoResponseText); + } + } + + /// + /// Returns URI-safe data with a given input length. + /// + /// Input length (nb. output will be longer) + /// + public static string randomDataBase64url(uint length) + { + RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); + byte[] bytes = new byte[length]; + rng.GetBytes(bytes); + return base64urlencodeNoPadding(bytes); + } + + /// + /// Returns the SHA256 hash of the input string. + /// + /// + /// + public static byte[] sha256(string inputString) + { + byte[] bytes = Encoding.ASCII.GetBytes(inputString); + SHA256Managed sha256 = new SHA256Managed(); + return sha256.ComputeHash(bytes); + } + + /// + /// Base64url no-padding encodes the given input buffer. + /// + /// + /// + public static string base64urlencodeNoPadding(byte[] buffer) + { + string base64 = Convert.ToBase64String(buffer); + + // Converts base64 to base64url. + base64 = base64.Replace("+", "-"); + base64 = base64.Replace("/", "_"); + // Strips padding. + base64 = base64.Replace("=", ""); + + return base64; + } + + // Hack to bring the Console window to front. + // ref: http://stackoverflow.com/a/12066376 + + [DllImport("kernel32.dll", ExactSpelling = true)] + public static extern IntPtr GetConsoleWindow(); + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetForegroundWindow(IntPtr hWnd); + + public void BringConsoleToFront() + { + SetForegroundWindow(GetConsoleWindow()); + } + } +} diff --git a/OAuthConsoleAppCpp/CSharpLib/CSharpLib.csproj b/OAuthConsoleAppCpp/CSharpLib/CSharpLib.csproj new file mode 100644 index 0000000..fe52c84 --- /dev/null +++ b/OAuthConsoleAppCpp/CSharpLib/CSharpLib.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601} + Library + Properties + CSharpLib + CSharpLib + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OAuthConsoleAppCpp/CSharpLib/Properties/AssemblyInfo.cs b/OAuthConsoleAppCpp/CSharpLib/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..95ffdba --- /dev/null +++ b/OAuthConsoleAppCpp/CSharpLib/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("CSharpLib")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CSharpLib")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[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("afba92ce-b819-4d08-a3fe-4453ae357601")] + +// 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/OAuthConsoleAppCpp/CSharpLib/packages.config b/OAuthConsoleAppCpp/CSharpLib/packages.config new file mode 100644 index 0000000..7ee8c10 --- /dev/null +++ b/OAuthConsoleAppCpp/CSharpLib/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/OAuthConsoleAppCpp/OAuthConsoleAppCpp.sln b/OAuthConsoleAppCpp/OAuthConsoleAppCpp.sln new file mode 100644 index 0000000..0d4f1fa --- /dev/null +++ b/OAuthConsoleAppCpp/OAuthConsoleAppCpp.sln @@ -0,0 +1,69 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpLib", "CSharpLib\CSharpLib.csproj", "{AFBA92CE-B819-4D08-A3FE-4453AE357601}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CLRLib", "CLRLib\CLRLib.vcxproj", "{148070F3-1952-4D2F-BCEB-8CB646D0FBCD}" + ProjectSection(ProjectDependencies) = postProject + {AFBA92CE-B819-4D08-A3FE-4453AE357601} = {AFBA92CE-B819-4D08-A3FE-4453AE357601} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OAuthConsoleAppCpp", "OAuthConsoleAppCpp\OAuthConsoleAppCpp.vcxproj", "{320EDED2-0494-4BAE-9F94-DDF7A3B49872}" + ProjectSection(ProjectDependencies) = postProject + {AFBA92CE-B819-4D08-A3FE-4453AE357601} = {AFBA92CE-B819-4D08-A3FE-4453AE357601} + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD} = {148070F3-1952-4D2F-BCEB-8CB646D0FBCD} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Debug|Any CPU.ActiveCfg = Release|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Debug|Any CPU.Build.0 = Release|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Debug|x64.ActiveCfg = Debug|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Debug|x64.Build.0 = Debug|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Debug|x86.ActiveCfg = Debug|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Debug|x86.Build.0 = Debug|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Release|Any CPU.Build.0 = Release|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Release|x64.ActiveCfg = Release|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Release|x64.Build.0 = Release|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Release|x86.ActiveCfg = Release|Any CPU + {AFBA92CE-B819-4D08-A3FE-4453AE357601}.Release|x86.Build.0 = Release|Any CPU + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Debug|Any CPU.ActiveCfg = Release|Win32 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Debug|Any CPU.Build.0 = Release|Win32 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Debug|x64.ActiveCfg = Debug|x64 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Debug|x64.Build.0 = Debug|x64 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Debug|x86.ActiveCfg = Debug|Win32 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Debug|x86.Build.0 = Debug|Win32 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Release|Any CPU.ActiveCfg = Release|Win32 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Release|Any CPU.Build.0 = Release|Win32 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Release|x64.ActiveCfg = Release|x64 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Release|x64.Build.0 = Release|x64 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Release|x86.ActiveCfg = Release|Win32 + {148070F3-1952-4D2F-BCEB-8CB646D0FBCD}.Release|x86.Build.0 = Release|Win32 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Debug|Any CPU.ActiveCfg = Release|Win32 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Debug|Any CPU.Build.0 = Release|Win32 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Debug|x64.ActiveCfg = Debug|x64 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Debug|x64.Build.0 = Debug|x64 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Debug|x86.ActiveCfg = Debug|Win32 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Debug|x86.Build.0 = Debug|Win32 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Release|Any CPU.ActiveCfg = Release|Win32 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Release|Any CPU.Build.0 = Release|Win32 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Release|x64.ActiveCfg = Release|x64 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Release|x64.Build.0 = Release|x64 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Release|x86.ActiveCfg = Release|Win32 + {320EDED2-0494-4BAE-9F94-DDF7A3B49872}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp new file mode 100644 index 0000000..5df4ca8 --- /dev/null +++ b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp @@ -0,0 +1,41 @@ +// Copyright 2020 Wiktor Lawski +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include "CLRLib.h" + +using OAuthConsoleAppCpp::CLRLib; + +void output(const char * output) +{ + printf("%s\n", output); +} + +int main() +{ + const auto clientID = "581786658708-elflankerquo1a6vsckabbhn25hclla0.apps.googleusercontent.com"; + const auto clientSecret = "3f6NggMbPtrmIBpgx-MK2xXK"; + + CLRLib::setOutputCallback(output); + + CLRLib clrLib; + + clrLib.printPrompt(); + clrLib.performAuth(clientID, clientSecret); + + getchar(); + + return 0; +} \ No newline at end of file diff --git a/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.vcxproj b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.vcxproj new file mode 100644 index 0000000..e3a7c65 --- /dev/null +++ b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.vcxproj @@ -0,0 +1,168 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {320EDED2-0494-4BAE-9F94-DDF7A3B49872} + Win32Proj + OAuthConsoleAppCpp + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + NotUsing + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\CLRLib + + + Console + true + CLRLib.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\$(ConfigurationName) + + + xcopy "$(SolutionDir)\CSharpLib\bin\$(ConfigurationName)\CSharpLib.dll" "$(TargetDir)" /Y && xcopy "$(SolutionDir)\CSharpLib\bin\$(ConfigurationName)\Newtonsoft.Json.dll" "$(TargetDir)" /Y + + + + + Use + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\CLRLib + + + Console + true + true + true + CLRLib.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + $(SolutionDir)\$(ConfigurationName) + + + xcopy "$(SolutionDir)\CSharpLib\bin\$(ConfigurationName)\CSharpLib.dll" "$(TargetDir)" /Y && xcopy "$(SolutionDir)\CSharpLib\bin\$(ConfigurationName)\Newtonsoft.Json.dll" "$(TargetDir)" /Y + + + + + Level3 + Use + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.vcxproj.filters b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.vcxproj.filters new file mode 100644 index 0000000..8b83876 --- /dev/null +++ b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/OAuthConsoleAppCpp/OAuthConsoleAppCpp/ReadMe.txt b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/ReadMe.txt new file mode 100644 index 0000000..d7f61ad --- /dev/null +++ b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/ReadMe.txt @@ -0,0 +1,40 @@ +======================================================================== + CONSOLE APPLICATION : OAuthConsoleAppCpp Project Overview +======================================================================== + +AppWizard has created this OAuthConsoleAppCpp application for you. + +This file contains a summary of what you will find in each of the files that +make up your OAuthConsoleAppCpp application. + + +OAuthConsoleAppCpp.vcxproj + This is the main project file for VC++ projects generated using an Application Wizard. + It contains information about the version of Visual C++ that generated the file, and + information about the platforms, configurations, and project features selected with the + Application Wizard. + +OAuthConsoleAppCpp.vcxproj.filters + This is the filters file for VC++ projects generated using an Application Wizard. + It contains information about the association between the files in your project + and the filters. This association is used in the IDE to show grouping of files with + similar extensions under a specific node (for e.g. ".cpp" files are associated with the + "Source Files" filter). + +OAuthConsoleAppCpp.cpp + This is the main application source file. + +///////////////////////////////////////////////////////////////////////////// +Other standard files: + +StdAfx.h, StdAfx.cpp + These files are used to build a precompiled header (PCH) file + named OAuthConsoleAppCpp.pch and a precompiled types file named StdAfx.obj. + +///////////////////////////////////////////////////////////////////////////// +Other notes: + +AppWizard uses "TODO:" comments to indicate parts of the source code you +should add to or customize. + +///////////////////////////////////////////////////////////////////////////// diff --git a/OAuthConsoleAppCpp/OAuthConsoleAppCpp/targetver.h b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/targetver.h new file mode 100644 index 0000000..87c0086 --- /dev/null +++ b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/OAuthConsoleAppCpp/README.md b/OAuthConsoleAppCpp/README.md new file mode 100644 index 0000000..a8c96e6 --- /dev/null +++ b/OAuthConsoleAppCpp/README.md @@ -0,0 +1,92 @@ +OAuth for Apps: Sample Console Application for Windows +====================================================== + +This sample shows how to do an OAuth 2.0 Authorization flow from a Windows +Console application. It is one of a [series of OAuth samples](../README.md) +for Windows. + +Introduction +------------ + +When doing an OAuth 2.0 Authorization flow in a native application, it is +important to follow best practices, which require using the browser (and not +an embedded browser). + +This sample demonstrates how you can open the user's browser with your OAuth 2.0 +authorization request (where they might already be logged in!), have them +complete the consent, receive the Authorization Code using a local loopback +socket, and exchanging that code for authorization tokens. + +Google Documentation +-------------------- + +The protocols referenced in this sample are documented here: + +- [OAuth 2.0](https://developers.google.com/identity/protocols/OAuth2) +- [Using OAuth 2.0 for Mobile and Desktop Applications](https://developers.google.com/identity/protocols/OAuth2InstalledApp) + +Getting Started +--------------- + +1. Open the solution file: `OAuthConsoleAppCpp.sln` +2. Run the app. +3. When the app starts, tap any key and go through the flow. +4. Tap any key to exit. + + +Using your own credentials +-------------------------- + +The Sample comes backed with some demo client credentials, which are fine for +testing, but make sure you use your own credentials before releasing any app, +or sharing it with friends. + +1. Visit the [Credentials page of the Developers Console](https://console.developers.google.com/apis/credentials?project=_) +2. Create a new OAuth 2.0 client, select `Other` +3. Copy the client id and client secret, and replace the values supplied in this + sample. + + +Support +------- + +If you have a question related to these samples, or Google OAuth in general, +please ask on Stack Overflow with the `google-oauth` tag + https://stackoverflow.com/questions/tagged/google-oauth + +If you've found an error in this sample, please file an issue: +https://github.com/googlesamples/oauth-apps-for-windows/issues + +Patches are encouraged, and may be submitted by forking this project and +submitting a pull request through GitHub. + +Advanced Reading +---------------- + +The protocols and best practices used and implemented in these samples are +defined by RFCs. These expert-level documents detail how the protocols work, +and explain the reasoning behind many decisions, such as why we send a +`code_challenge` on the Authorization Request for a native app. + +- [Internet-Draft: OAuth 2.0 for Native Apps BCP](https://tools.ietf.org/html/draft-ietf-oauth-native-apps) +- [RFC6749: OAuth 2.0](https://tools.ietf.org/html/rfc6749) +- [RFC6750: OAuth 2.0 Bearer Token Usage](https://tools.ietf.org/html/rfc6750) +- [RFC6819: OAuth 2.0 Threat Model and Security Considerations](https://tools.ietf.org/html/rfc6819) +- [RFC7636: OAuth 2.0 PKCE](https://tools.ietf.org/html/rfc7636) + +License +------- + +Copyright 2016 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/README.md b/README.md index 0763dc4..e887397 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,10 @@ Samples application sample (using WPF). [OAuthConsoleApp](OAuthConsoleApp/README.md) - Command Line Interface (CLI) -console application sample. +console application sample (C# interface). + +[OAuthConsoleAppCpp](OAuthConsoleApp/README.md) - Command Line Interface (CLI) +console application sample (C++ interface). All samples achieve the same end result of authenticating the user in the system browser, but with environment-specific optimizations. From 528caa07286d80307fdac02690bd5411b8d13d79 Mon Sep 17 00:00:00 2001 From: Wiktor Lawski Date: Mon, 19 Oct 2020 21:13:45 +0200 Subject: [PATCH 2/3] Correctly display non-ANSI response characters from C++ --- OAuthConsoleAppCpp/CLRLib/CLRLib.cpp | 4 ++-- OAuthConsoleAppCpp/CLRLib/CLRLib.h | 2 +- .../OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/OAuthConsoleAppCpp/CLRLib/CLRLib.cpp b/OAuthConsoleAppCpp/CLRLib/CLRLib.cpp index d130680..aa7619e 100644 --- a/OAuthConsoleAppCpp/CLRLib/CLRLib.cpp +++ b/OAuthConsoleAppCpp/CLRLib/CLRLib.cpp @@ -30,9 +30,9 @@ namespace void outputCallbackWrapper(String^ output) { - auto outputPtr = Marshal::StringToHGlobalAnsi(output).ToPointer(); + auto outputPtr = Marshal::StringToHGlobalUni(output).ToPointer(); - currentCallback(static_cast(outputPtr)); + currentCallback(static_cast(outputPtr)); Marshal::FreeHGlobal(static_cast(outputPtr)); } diff --git a/OAuthConsoleAppCpp/CLRLib/CLRLib.h b/OAuthConsoleAppCpp/CLRLib/CLRLib.h index 003e861..9c58a9b 100644 --- a/OAuthConsoleAppCpp/CLRLib/CLRLib.h +++ b/OAuthConsoleAppCpp/CLRLib/CLRLib.h @@ -14,7 +14,7 @@ #pragma once -typedef void(__cdecl *outputCallback)(const char *); +typedef void(__cdecl *outputCallback)(const wchar_t *); namespace OAuthConsoleAppCpp { diff --git a/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp index 5df4ca8..65f27bf 100644 --- a/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp +++ b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp @@ -13,14 +13,18 @@ // limitations under the License. #include +#include +#include #include "CLRLib.h" using OAuthConsoleAppCpp::CLRLib; -void output(const char * output) +void output(const wchar_t * output) { - printf("%s\n", output); + _setmode(_fileno(stdout), _O_U16TEXT); + wprintf(L"%s\n", output); + _setmode(_fileno(stdout), _O_TEXT); } int main() From 387f2a5538def6bf5f06a226b130605a0578fdc1 Mon Sep 17 00:00:00 2001 From: Wiktor Lawski Date: Mon, 19 Oct 2020 21:57:29 +0200 Subject: [PATCH 3/3] Don't assume current output mode Output mode will be restored to the original one after each use. --- .../OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp index 65f27bf..09ce156 100644 --- a/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp +++ b/OAuthConsoleAppCpp/OAuthConsoleAppCpp/OAuthConsoleAppCpp.cpp @@ -22,9 +22,11 @@ using OAuthConsoleAppCpp::CLRLib; void output(const wchar_t * output) { - _setmode(_fileno(stdout), _O_U16TEXT); + const auto originalMode = _setmode(_fileno(stdout), _O_U16TEXT); + wprintf(L"%s\n", output); - _setmode(_fileno(stdout), _O_TEXT); + + _setmode(_fileno(stdout), originalMode); } int main()