Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.
Open
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
71 changes: 71 additions & 0 deletions .github/workflows/publish-nupkg.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Build and Publish to Azure Artifacts / GitHub Packages

on:
release:
types: [published]

env:
AZURE_ARTIFACTS_FEED_URL: https://pkgs.dev.azure.com/intuitionps/01c84548-9607-4655-80e7-6ad95390a38c/_packaging/private/nuget/v3/index.json
GITHUB_PACKAGES_URL: https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json
PROJECT_NAME: Ellucian.Ethos.Integration
BUILD_CONFIGURATION: 'Release' # set this to the appropriate build configuration
DOTNET_VERSION: '6.x'

jobs:
build:
runs-on: windows-latest
steps:
# Checkout the repo
- uses: actions/checkout@v2

# Setup .NET Core SDK
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

# Run dotnet build and test
- name: dotnet build and test
run: |
dotnet nuget add source ${{ env.GITHUB_PACKAGES_URL }} --name intuitionps --username ${{ github.actor }} --password ${{ secrets.GH_PACKAGES_PAT }}
dotnet nuget add source https://api.nuget.org/v3/index.json -name nugetorg
dotnet restore
dotnet build --configuration '${{ env.BUILD_CONFIGURATION }}'
dotnet test --configuration '${{ env.BUILD_CONFIGURATION }}'

az-artifacts-build-and-deploy:
needs: build
runs-on: windows-latest
steps:
# Checkout the repo
- uses: actions/checkout@v2

# Extract git version
- name: Extract git tag
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV

# Setup .NET Core SDK
- name: Setup .NET Core
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
source-url: ${{ env.AZURE_ARTIFACTS_FEED_URL }}
env:
NUGET_AUTH_TOKEN: ${{ secrets.AZURE_ARTIFACTS_PAT }}

# Run dotnet build and package
- name: dotnet build and publish
run: |
dotnet nuget add source ${{ env.GITHUB_PACKAGES_URL }} --name intuitionps --username ${{ github.actor }} --password ${{ secrets.GH_PACKAGES_PAT }}
dotnet nuget add source https://api.nuget.org/v3/index.json -name nugetorg
dotnet restore
dotnet build --configuration '${{ env.BUILD_CONFIGURATION }}' /p:Version=${{ github.event.release.tag_name }}
dotnet pack -c '${{ env.BUILD_CONFIGURATION }}'

# Publish the package to Azure Artifacts | must specify our version since the ellucian-developer/integration-sdk-csharp gets packaged as well
- name: 'dotnet publish to Azure Artifacts'
run: dotnet nuget push --api-key AzureArtifacts ${{ env.PROJECT_NAME }}\bin\Release\${{ env.PROJECT_NAME }}.${{ github.event.release.tag_name }}.nupkg

# Publish the package to GitHub Packages | must specify our version since the ellucian-developer/integration-sdk-csharp gets packaged as well
- name: 'dotnet publish to GitHub Packages'
run: dotnet nuget push --api-key ${{ secrets.GH_PACKAGES_PAT }} --source ${{ env.GITHUB_PACKAGES_URL }} ${{ env.PROJECT_NAME }}\bin\Release\${{ env.PROJECT_NAME }}.${{ github.event.release.tag_name }}.nupkg
14 changes: 14 additions & 0 deletions ColleagueApiExample/ColleagueApiExample.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Ellucian.Ethos.Integration\Ellucian.Ethos.Integration.csproj" />
</ItemGroup>

</Project>
37 changes: 37 additions & 0 deletions ColleagueApiExample/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Ellucian.Ethos.Integration.Client;
using Ellucian.Ethos.Integration.Client.Filter.Extensions;
using Ellucian.Ethos.Integration.Client.Proxy.Filter;
using Newtonsoft.Json.Linq;

var proxyClient =
new EthosClientBuilder(
colleagueApiUrl: "",
colleagueApiUsername: "",
colleagueApiPassword: "")
.BuildColleagueWebApiProxyclient();

var academicPeriod =
await proxyClient.GetAsJObjectByIdAsync("academic-periods", "a4b5fddc-fa2f-4e94-82e9-cbe219a5029b");

Console.WriteLine(academicPeriod.ToString());

var queryClient =
new EthosClientBuilder(
colleagueApiUrl: "",
colleagueApiUsername: "",
colleagueApiPassword: "")
.BuildColleagueWebApiFilterQueryClient();

var filter =
new CriteriaFilter()
.WithSimpleCriteria("startOn", ("$gte", "2020-01-01"));

var responses =
await queryClient.GetPagesWithCriteriaFilterAsync("academic-periods", filter);

foreach (var response in responses)
{
var acadPeriods = JArray.Parse(response.Content);

Console.WriteLine(acadPeriods.ToString());
}
17 changes: 17 additions & 0 deletions Ellucian.Ethos.Integration.sln
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{80815184
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Src", "Src", "{1402F60B-E306-4A41-B41E-2EF9136DFB36}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{B29BB850-080B-4A04-AF80-BB1B3FD91BC9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColleagueApiExample", "ColleagueApiExample\ColleagueApiExample.csproj", "{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -51,13 +55,26 @@ Global
{DC26CC20-E923-4AA4-93C2-BCBEFC10EF26}.Release|x64.Build.0 = Release|Any CPU
{DC26CC20-E923-4AA4-93C2-BCBEFC10EF26}.Release|x86.ActiveCfg = Release|Any CPU
{DC26CC20-E923-4AA4-93C2-BCBEFC10EF26}.Release|x86.Build.0 = Release|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Debug|x64.ActiveCfg = Debug|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Debug|x64.Build.0 = Debug|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Debug|x86.ActiveCfg = Debug|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Debug|x86.Build.0 = Debug|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Release|Any CPU.Build.0 = Release|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Release|x64.ActiveCfg = Release|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Release|x64.Build.0 = Release|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Release|x86.ActiveCfg = Release|Any CPU
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{34B4B30E-C0E0-43E9-88D0-27B31BA01574} = {1402F60B-E306-4A41-B41E-2EF9136DFB36}
{DC26CC20-E923-4AA4-93C2-BCBEFC10EF26} = {80815184-6446-4B0F-B7A4-B190E5E53F70}
{41EC6B03-53B4-43FC-AA8B-0E74C143F2FD} = {B29BB850-080B-4A04-AF80-BB1B3FD91BC9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB6A0A9A-4183-491A-97B0-86939AB919E1}
Expand Down
8 changes: 6 additions & 2 deletions Ellucian.Ethos.Integration/Authentication/SupportedRegions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public enum SupportedRegions
/// <summary>
/// Europe.
/// </summary>
Europe
}
Europe,
/// <summary>
/// Self-Hosted.
/// </summary>
SelfHosted
}
}
62 changes: 57 additions & 5 deletions Ellucian.Ethos.Integration/Client/EthosClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,24 @@ public class EthosClient
/// </summary>
private string ApiKey { get; }

// protected EthosEthosIntegrationUrls EthosIntegrationUrls = new EthosEthosIntegrationUrls();

// Only used by ColleagueWebAPIProxyClients
/// <summary>
/// Api URL to Self-Hosted Colleague API.
/// </summary>
protected string ColleagueApiUrl;
/// <summary>
/// Self-Hosted Colleague API username.
/// </summary>
protected string ColleagueApiUsername;
/// <summary>
/// Self-Hosted Colleague API password.
/// </summary>
protected string ColleagueApiPassword;



/// <summary>
/// Default token expiration time.
/// </summary>
Expand Down Expand Up @@ -102,6 +120,31 @@ public EthosClient( string apiKey, HttpClient client )
this.HttpProtocolClientBuilder ??= new HttpProtocolClientBuilder( client );
EthosResponseBuilder ??= new EthosResponseBuilder();
}
/// <summary>
/// Constructor called by subclasses of this class, specifically for ColleagueWebApiProxies.
/// </summary>
/// <param name="colleagueApiUrl">The URL to the Colleague API instance. If it is null/empty, then an <see cref="ArgumentNullException"/> will be thrown.</param>
/// <param name="colleagueApiUsername">The username used to connect to the Colleague API. If it is null/empty, then an <see cref="ArgumentNullException"/> will be thrown.</param>
/// <param name="colleagueApiPassword">The password used to connect to the Colleague API. If it is null/empty, then an <see cref="ArgumentNullException"/> will be thrown.</param>
/// <param name="client">A <see cref="System.Net.Http.HttpClient"/>.</param>
public EthosClient(string colleagueApiUrl, string colleagueApiUsername, string colleagueApiPassword, HttpClient client)
{
if (colleagueApiUrl == null || colleagueApiUsername == null
|| colleagueApiPassword == null || colleagueApiPassword == null)
{
throw new ArgumentNullException($"Colleague API URL and Credentials are required.");
}
if (client == null)
{
throw new ArgumentNullException($"The '{nameof(client)}' parameter is required.");
}
ApiKey = null;
this.ColleagueApiUrl = colleagueApiUrl;
this.ColleagueApiUsername = colleagueApiUsername;
this.ColleagueApiPassword = colleagueApiPassword;
this.HttpProtocolClientBuilder ??= new HttpProtocolClientBuilder(client);
EthosResponseBuilder ??= new EthosResponseBuilder();
}

#endregion

Expand Down Expand Up @@ -175,7 +218,7 @@ public async Task<AccessToken> GetAccessTokenAsync()
/// <exception cref="HttpRequestException">Returns <see cref="HttpRequestException"/> exception if the request fails.</exception>
private async Task<AccessToken> GetNewTokenAsync()
{
string authUrl = $"{ EthosIntegrationUrls.Auth( this.Region )}?expirationMinutes={ ExpirationMinutes }";
string authUrl = $"{EthosIntegrationUrls.Auth( this.Region )}?expirationMinutes={ ExpirationMinutes }";
HttpProtocolClientBuilder.Client.DefaultRequestHeaders.Add( "Authorization", $"Bearer { ApiKey }" );
//make request
HttpResponseMessage response = await HttpProtocolClientBuilder.Client.PostAsync( new Uri( authUrl ), null );
Expand Down Expand Up @@ -369,11 +412,20 @@ public async Task DeleteAsync( Dictionary<string, string> headers, string url )
/// <returns>A <see cref="Task"/></returns>
private async Task AddAccessTokenAuthHeaderAsync( Dictionary<string, string> headers )
{
AccessToken token = await GetAccessTokenAsync();
if ( token.GetAuthHeader().TryGetValue( "Authorization", out string authValue ) )
headers.Remove("Authorization");
if (Region == SupportedRegions.SelfHosted)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(ColleagueApiUsername + ":" + ColleagueApiPassword);
string token = System.Convert.ToBase64String(bytes);
headers.Add("Authorization", "Basic " + token);
}
else
{
headers.Remove( "Authorization" );
headers.Add( "Authorization", authValue );
AccessToken token = await GetAccessTokenAsync();
if (token.GetAuthHeader().TryGetValue("Authorization", out string authValue))
{
headers.Add("Authorization", authValue);
}
}
}

Expand Down
37 changes: 36 additions & 1 deletion Ellucian.Ethos.Integration/Client/EthosClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ public class EthosClientBuilder
/// </summary>
private int? ConnectionTimeout = null;

private readonly string ColleagueApiUrl;
private readonly string ColleagueApiUsername;
private readonly string ColleagueApiPassword;

/// <summary>
/// Interface used in HttpProtocolClientBuilder.
/// </summary>
Expand All @@ -42,6 +46,19 @@ public EthosClientBuilder( string apiKey )
this.apiKey = apiKey;
builder ??= new HttpProtocolClientBuilder( null, ConnectionTimeout );
}
/// <summary>
/// Constructs this class with the given Colleauge API URL/Credentials.
/// </summary>
/// <param name="colleagueApiUrl">The URL to the Colleague API instance.</param>
/// <param name="colleagueApiUsername">The username used to connect to the Colleague API.</param>
/// <param name="colleagueApiPassword">The password used to connect to the Colleague API.</param>
public EthosClientBuilder(string colleagueApiUrl, string colleagueApiUsername, string colleagueApiPassword)
{
builder ??= new HttpProtocolClientBuilder(null, ConnectionTimeout);
ColleagueApiUrl = colleagueApiUrl;
ColleagueApiUsername = colleagueApiUsername;
ColleagueApiPassword = colleagueApiPassword;
}

/// <summary>
/// Give the client factory a connection timeout so that connections will time out after <code>connectionTimeout</code>
Expand Down Expand Up @@ -97,7 +114,25 @@ public EthosMessagesClient BuildEthosMessagesClient()
/// <returns>An EthosFilterQueryClient using the given apiKey and timeout values.</returns>
public EthosFilterQueryClient BuildEthosFilterQueryClient()
{
return new EthosFilterQueryClient( apiKey, builder.Client );
return new EthosFilterQueryClient(apiKey, builder.Client);
}

/// <summary>
/// Gets an <see cref="EthosProxyClient"/> that will use the given Colleague credentials to authenticate.
/// </summary>
/// <returns>An ColleagueWebApiProxyClient using the given Colleague credentials.</returns>
public ColleagueWebApiProxyClient BuildColleagueWebApiProxyclient()
{
return new ColleagueWebApiProxyClient(ColleagueApiUrl, ColleagueApiUsername, ColleagueApiPassword, builder.Client);
}

/// <summary>
/// Gets an <see cref="ColleagueWebApiFilterQueryClient"/> that will use the Colleague credentials to authenticate.
/// </summary>
/// <returns>An ColleagueWebApiFilterQueryClient using the given Colleague credentials.</returns>
public ColleagueWebApiFilterQueryClient BuildColleagueWebApiFilterQueryClient()
{
return new ColleagueWebApiFilterQueryClient(ColleagueApiUrl, ColleagueApiUsername, ColleagueApiPassword, builder.Client);
}
}
}
11 changes: 4 additions & 7 deletions Ellucian.Ethos.Integration/Client/HttpProtocolClientBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ public class HttpProtocolClientBuilder : IHttpProtocolClientBuilder
/// Time in seconds to allow an http connection to time out. Default is
/// 300 seconds (5 minutes).
/// </summary>
private static int CONNECTION_TIMEOUT = 300;
private static readonly int CONNECTION_TIMEOUT = 300;

private const SslProtocols PROTOCOL = SslProtocols.Tls13 | SslProtocols.Tls12 | SslProtocols.Tls11;
private const SslProtocols PROTOCOL = SslProtocols.Tls13 | SslProtocols.Tls12;

private const string CLIENT_NAME = "EllucianEthosIntegrationSdk-dotnet";

Expand All @@ -44,10 +44,7 @@ public class HttpProtocolClientBuilder : IHttpProtocolClientBuilder
/// 300 seconds (5 minutes).</param>
public HttpProtocolClientBuilder( HttpClient client, int? connectionTimeOut = null )
{
if ( client == null )
{
client = BuildHttpClient( connectionTimeOut.HasValue ? connectionTimeOut : CONNECTION_TIMEOUT );
}
client ??= BuildHttpClient( connectionTimeOut.HasValue ? connectionTimeOut : CONNECTION_TIMEOUT );

Client = client;
}
Expand All @@ -66,7 +63,7 @@ public HttpClient BuildHttpClient( int? connectionTimeout )
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Add( "pragma", "no-cache" );
client.DefaultRequestHeaders.Add( "cache-control", "no-cache" );
ProductInfoHeaderValue prodHeaderVal = new ProductInfoHeaderValue( CLIENT_NAME, Assembly.GetExecutingAssembly().GetName()?.Version?.ToString() );
ProductInfoHeaderValue prodHeaderVal = new( CLIENT_NAME, Assembly.GetExecutingAssembly().GetName()?.Version?.ToString() );
client.DefaultRequestHeaders.UserAgent.Add( prodHeaderVal );
client.Timeout = TimeSpan.FromMinutes( ( double ) connectionTimeout );
} )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ private async Task<IEnumerable<ChangeNotification>> GetMessagesAsync( int? limit
/// <returns>The number of available messages in the application's queue.</returns>
public async Task<int> GetNumAvailableMessagesAsync()
{
EthosResponse response = await HeadAsync( EthosIntegrationUrls.Consume( Region, -1, -1 ) );
EthosResponse response = await HeadAsync(EthosIntegrationUrls.Consume( Region, -1, -1 ) );
string remaining = response.GetHeader( "x-remaining" );
if ( int.TryParse( remaining, out int numMessages ) )
{
Expand Down
Loading