Skip to content
Merged

5.0 #69

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions Acumatica REST API Console Application/OAuthHybridExample.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using Acumatica.Default_24_200_001.Model;
using Acumatica.RESTClient.AuthApi.Model;
using Acumatica.RESTClient.Client;
using Acumatica.RESTClient.Loggers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using static Acumatica.RESTClient.AuthApi.AuthApiExtensions;
using static Acumatica.RESTClient.ContractBasedApi.ApiClientExtensions;
using Task = System.Threading.Tasks.Task;

namespace AcumaticaRestApiExample
{
internal class OAuthHybridExample
{
public static void Example(string siteURL, string clientSecret, string clientID, string redirectUrl)
{
var client = new ApiClient(siteURL,
requestInterceptor: FileRequestLogger.LogRequest
// ,responseInterceptor: RequestLogger.LogResponse
, ignoreSslErrors: true // this is here to allow testing with self-signed certificates
);
bool usePost = true; //This is here to read the the Authorization data in backend
var url = client.Authorize(
clientID,
clientSecret,
redirectUrl,
OAuthScope.API | OAuthScope.OfflineAccess | OAuthScope.OpenID,
ResponseType.IdToken,// | ResponseType.Token,
usePost,
Guid.NewGuid().ToString()
);
OpenUrl(url);

var code = ReadCodeFromRedirectURL(redirectUrl, usePost, out Token token);

client.ReceiveAccessTokenAuthCode(
clientID,
clientSecret,
redirectUrl,
code);

foreach (var account in client.GetList<Account>(top: 5))
{
Console.WriteLine(account.Description.Value);
}
client.TryLogout();

foreach (var soorder in client.GetList<SalesOrder>(top: 5))
{
Console.WriteLine(soorder.Description.Value);
}
client.TryLogout();

}

private static string ReadCodeFromRedirectURL(string url, bool usePost, out Token token)
{
HttpListener listener = new HttpListener();
listener.Prefixes.Add(url);
token = new Token();
try
{
listener.Start();
string purecode = string.Empty;

HttpListenerContext context = listener.GetContext();
HttpListenerRequest request = context.Request;
if (usePost)
{
using (System.IO.Stream body = context.Request.InputStream) // here we have data
{
using (var reader = new System.IO.StreamReader(body, context.Request.ContentEncoding))
{
var values = HttpUtility.ParseQueryString(reader.ReadToEnd());
Console.WriteLine();
token.Access_token = values.Get("access_token");
token.Expires_in = values.Get("expires_in");
token.Token_type = values.Get("token_type");
token.Scope = values.Get("scope");
purecode = values.Get("code") ?? string.Empty;
}
}
}
else
{
var rawUrl = request.RawUrl;
const string codeParametrIdentifier = "?code=";
var codewithgarbage = rawUrl.Substring(rawUrl.IndexOf(codeParametrIdentifier) + codeParametrIdentifier.Length);
purecode = codewithgarbage.Substring(0, codewithgarbage.IndexOf("&"));
}
return purecode;
}
finally
{
listener.Stop();
}
}


private static void OpenUrl(string url)
{
try
{
Process.Start(url);
}
catch
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Process.Start("xdg-open", url);
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Process.Start("open", url);
}
else
{
throw;
}
}
}
}
}
18 changes: 14 additions & 4 deletions Acumatica REST API Console Application/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,17 @@ class Program
private const string ClientSecretAC = "ZJ7sGGDZWOJyJzSpbRjrpg";
private const string ClientIDAC = "9737F884-8405-42FB-7303-7F1DA7BE8CA7@Company";
#endregion
# region Hybrid flow
private const string ClientSecretHybrid = "KzK82VVdqggy4PHaOHMTNw";
private const string ClientIDHybrid = "2DC9435C-A596-959E-3E38-8EB84725F089@Company";
#endregion
const string RedirectUrl = "https://localhost/test/";

static async Task Main(string[] args)
{
Console.WriteLine("Update example");
Console.WriteLine("----------------------------------------");
RESTExample.UpdateBill(SiteURL, Username, Password, Tenant, Branch, Locale);
{
Console.WriteLine("Update example");
Console.WriteLine("----------------------------------------");
RESTExample.UpdateBill(SiteURL, Username, Password, Tenant, Branch, Locale);


Console.WriteLine("Report example");
Expand Down Expand Up @@ -80,6 +84,12 @@ static async Task Main(string[] args)
OAuthAuthCodeExample.Example(SiteURL, ClientSecretAC, ClientIDAC, RedirectUrl);
Console.WriteLine("Ready to continue...");
Console.ReadLine();

Console.WriteLine("OAuth 2.0 (Hybrid flow)");
Console.WriteLine("----------------------------------------");
OAuthHybridExample.Example(SiteURL, ClientSecretHybrid, ClientIDHybrid, RedirectUrl);
Console.WriteLine("Ready to continue...");
Console.ReadLine();
//await TestPerformanceAsync();

}
Expand Down
58 changes: 46 additions & 12 deletions Acumatica.RESTClient/AuthApi/AuthApi.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
using Acumatica.RESTClient.Api;
using Acumatica.RESTClient.AuthApi.Model;
using Acumatica.RESTClient.Client;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Acumatica.RESTClient.Api;
using Acumatica.RESTClient.AuthApi.Model;
using Acumatica.RESTClient.Client;

using static Acumatica.RESTClient.Auxiliary.ApiClientHelpers;


Expand Down Expand Up @@ -117,9 +115,12 @@ public async static Task ReceiveAccessTokenAsync(
/// <param name="clientSecret"></param>
/// <param name="redirectUrl"></param>
/// <param name="scope"></param>
public static string Authorize(this ApiClient client, string clientID, string clientSecret, string redirectUrl, OAuthScope scope)
/// <param name="responseType"></param>
/// <param name="usePost"></param>
/// <param name="nonce"></param>
public static string Authorize(this ApiClient client, string clientID, string clientSecret, string redirectUrl, OAuthScope scope, ResponseType? responseType = null, bool usePost = false, string nonce = "")
{
return AuthorizeAsync(client, clientID, clientSecret, redirectUrl, scope).Result;
return AuthorizeAsync(client, clientID, clientSecret, redirectUrl, scope, responseType: responseType, usePost: usePost, nonce: nonce).Result;
}
/// <summary>
///
Expand All @@ -130,20 +131,28 @@ public static string Authorize(this ApiClient client, string clientID, string cl
/// <param name="redirectUrl"></param>
/// <param name="scope"></param>
/// <param name="cancellationToken"></param>
/// <param name="responseType"></param>
/// <param name="usePost"></param>
/// <param name="nonce"></param>
public async static Task<string> AuthorizeAsync(
this ApiClient client,
string clientID,
string clientSecret,
string redirectUrl,
OAuthScope scope,
CancellationToken cancellationToken = default)
CancellationToken cancellationToken = default,
ResponseType? responseType = null,
bool usePost = false,
string nonce = "")
{
List<KeyValuePair<string, string>> queryParams = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("response_type", "code"),
new KeyValuePair<string, string>("response_type", PrepareResponseType(responseType)),
new KeyValuePair<string, string>("client_id", clientID),
new KeyValuePair<string, string>("scope", PrepareScopeParameter(scope)),
new KeyValuePair<string, string>("redirect_uri", redirectUrl)
new KeyValuePair<string, string>("redirect_uri", redirectUrl),
new KeyValuePair<string, string>("response_mode", usePost ? "form_post" : "fragment"),
new KeyValuePair<string, string>("nonce", nonce)
};

HttpResponseMessage response = await client.CallApiAsync(
Expand Down Expand Up @@ -385,7 +394,30 @@ public enum OAuthScope
None = 0,
API = 1,
OfflineAccess = 2,
ConcurrentAccess = 4
ConcurrentAccess = 4,
OpenID = 8
}

[Flags]
public enum ResponseType
{
IdToken = 0,
Token = 1
}

private static string PrepareResponseType(ResponseType? responseType)
{
StringBuilder s = new StringBuilder();
s.Append("code");
if (responseType != null)
{
if (responseType.Value.HasFlag(ResponseType.IdToken))
s.Append(" id_token");
if (responseType.Value.HasFlag(ResponseType.Token))
s.Append(" token");
}

return s.ToString().TrimEnd(' ');
}

private static string PrepareScopeParameter(OAuthScope scope)
Expand All @@ -396,7 +428,9 @@ private static string PrepareScopeParameter(OAuthScope scope)
if (scope.HasFlag(OAuthScope.OfflineAccess))
s.Append("offline_access ");
if (scope.HasFlag(OAuthScope.ConcurrentAccess))
s.Append("api:concurrent_access ");
s.Append("api:concurrent_access ");
if (scope.HasFlag(OAuthScope.OpenID))
s.Append("openid ");

return s.ToString().TrimEnd(' ');
}
Expand Down