Skip to content

Commit

Permalink
Merge pull request #23 from wparad/add-last-modified-support
Browse files Browse the repository at this point in the history
Support expectedLastModifiedTime for access records and groups.
  • Loading branch information
wparad authored Jan 9, 2024
2 parents 619c9f3 + e8f4348 commit 3962d05
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This is the changelog for [Authress SDK](readme.md).
## 1.4 ##
* Support exponential back-off retries on unexpected failures.
* Add optimized caching for authorization checks
* Add support for If-Unmodified-Since in group and access record apis.

## 1.3 ##
* Upgrade to using dotnet 6.0 support.
Expand Down
59 changes: 59 additions & 0 deletions contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@

## Using the OpenAPI Generator to generate new models

#### Start container
```sh
podman pull docker://openapitools/openapi-generator-online
```

#### Start container at port 8888 and save the container id
```sh
CID=$(podman run -d -p 8888:8080 openapitools/openapi-generator-online)
sleep 10

# Execute an HTTP request to generate a Ruby client
curl -X POST --header 'Content-Type: application/json' --header 'Accept: application/json' -d '{"openAPIUrl": "https://api.authress.io/", "options": { "useSingleRequestParameter": true, "packageName": "authress", "packageVersion": "99.99.99" } }' 'http://localhost:8888/api/gen/clients/csharp'


# RESPONSE: { "link":"http://localhost:8888/api/gen/download/c2d483.3.4672-40e9-91df-b9ffd18d22b8" }
```

### Download the generated zip file
```sh

wget RESPONSE_LINK

# Unzip the file
unzip SHA

# Shutdown the openapi generator image
podman stop $CID && podman rm $CID
```

### Common review items
* [ ] Inputs to Constructor are (string: authress_api_url, string: service_client_access_key)
* [x] authress_api_url should sanitize https:// prefix and remove trailing `/`s
* [ ] Add authentication to the configuration class.
* [ ] Change configuration class name to be `AuthressSettings`
* Specify all the inputs to be consistent across languages
* [ ] constructors for classes should only have relevant input properties, for instances `links` are not required.
* [ ] Update documentation
* Make sure markdown is valid
* Remove unnecessary links
* Add first class examples to readme.md + api documentation
* Find failed UserId, RoleId, TenantId, GroupId, Action properties and convert them to strings
* [ ] Remove any unnecessary validations from object and parameter injection, often there are some even when properties are allowed to be null
* [ ] The service client code to generate a JWT from private key needs to be added
* [ ] Add UnauthorizedError type to the authorizeUser function
* [ ] GET query parameters should be an object
* [ ] Top level tags from the API should accessible from the base class: `authressClient.accessRecords.getRecords(...)`
* [ ] Automatic Retry
* [ ] Automatic fallback to cache
* [ ] In memory caching for authorization checks - memoize
* [ ] Unsigned int for all limits
* [ ] readonly properties are never specified as required for request bodies
* [ ] Update Documentation for the API
* [ ] Validate all enums are enums and can be null when they should be.
* [ ] Remove LocalHost from the docs
* [ ] Tests
* [x] If-unmodified-since should called `expectedLastModifiedTime`, accept string or dateTime and convert this to an ISO String
34 changes: 22 additions & 12 deletions src/Authress.SDK/Api/AccessRecordsApi.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
Expand Down Expand Up @@ -119,38 +120,47 @@ public async Task<AccessRecord> GetRecord (string recordId)
/// <summary>
/// Update an access record. Updates an access record adding or removing user permissions to resources.
/// </summary>
/// <param name="body"></param>
/// <param name="recordId">The identifier of the access record.</param>
/// /// <param name="recordId">The identifier of the access record.</param>
/// <param name="accessRecord"></param>
/// <param name="expectedLastModifiedTime">The expected last time the record was updated.</param>
/// <returns>AccessRecord</returns>
public async Task<AccessRecord> UpdateRecord (string recordId, AccessRecord body)
public async Task<AccessRecord> UpdateRecord (string recordId, AccessRecord accessRecord, DateTimeOffset? expectedLastModifiedTime = null)
{
// verify the required parameter 'body' is set
if (body == null) throw new ArgumentNullException("Missing required parameter 'body'.");
// verify the required parameter 'record' is set
if (accessRecord == null) throw new ArgumentNullException("Missing required parameter 'accessRecord'.");
// verify the required parameter 'recordId' is set
if (recordId == null) throw new ArgumentNullException("Missing required parameter 'recordId'.");

var path = $"/v1/records/{System.Web.HttpUtility.UrlEncode(recordId)}";
var client = await authressHttpClientProvider.GetHttpClientAsync();
using (var response = await client.PostAsync(path, body.ToHttpContent()))

using (var request = new HttpRequestMessage(HttpMethod.Post, path))
{
await response.ThrowIfNotSuccessStatusCode();
return await response.Content.ReadAsAsync<AccessRecord>();
request.Content = accessRecord.ToHttpContent();
if (expectedLastModifiedTime.HasValue) {
request.Headers.Add("If-Unmodified-Since", expectedLastModifiedTime.Value.ToString("o", CultureInfo.InvariantCulture));
}
using (var response = await client.PostAsync(path, accessRecord.ToHttpContent()))
{
await response.ThrowIfNotSuccessStatusCode();
return await response.Content.ReadAsAsync<AccessRecord>();
}
}
}

/// <summary>
/// Create access request Specify a request in the form of an access record that an admin can approve.
/// </summary>
/// <param name="body"></param>
/// <param name="accessRecord"></param>
/// <returns>AccessRequest</returns>
public async Task<AccessRequest> CreateRequest(AccessRequest body)
public async Task<AccessRequest> CreateRequest(AccessRequest accessRecord)
{
// verify the required parameter 'body' is set
if (body == null) throw new ArgumentNullException("Missing required parameter 'body'.");
if (accessRecord == null) throw new ArgumentNullException("Missing required parameter 'body'.");

var path = "/v1/requests";
var client = await authressHttpClientProvider.GetHttpClientAsync();
using (var response = await client.PostAsync(path, body.ToHttpContent()))
using (var response = await client.PostAsync(path, accessRecord.ToHttpContent()))
{
await response.ThrowIfNotSuccessStatusCode();
return await response.Content.ReadAsAsync<AccessRequest>();
Expand Down
21 changes: 15 additions & 6 deletions src/Authress.SDK/Api/GroupsApi.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
Expand Down Expand Up @@ -99,19 +100,27 @@ async public Task<GroupCollection> GetGroups (int? limit = null, string filter =
/// Update a group Updates a group adding or removing user. Change a group updates the permissions and roles the users have access to. (Groups have a maximum size of ~100KB)
/// </summary>
/// <param name="groupId">The identifier of the group.</param>
/// <param name="body"></param>
/// <param name="group">The Group data</param>
/// <param name="expectedLastModifiedTime">The expected last time the group was updated</param>
/// <returns>Group</returns>
public async Task<Group> UpdateGroup (string groupId, Group body)
public async Task<Group> UpdateGroup (string groupId, Group group, DateTimeOffset? expectedLastModifiedTime = null)
{
if (body == null) throw new ArgumentNullException("Missing required parameter 'body'.");
if (group == null) throw new ArgumentNullException("Missing required parameter 'group'.");
if (groupId == null) throw new ArgumentNullException("Missing required parameter 'groupId'.");

var path = $"/v1/groups/{System.Web.HttpUtility.UrlEncode(groupId)}";
var client = await authressHttpClientProvider.GetHttpClientAsync();
using (var response = await client.PostAsync(path, body.ToHttpContent()))
using (var request = new HttpRequestMessage(HttpMethod.Post, path))
{
await response.ThrowIfNotSuccessStatusCode();
return await response.Content.ReadAsAsync<Group>();
request.Content = group.ToHttpContent();
if (expectedLastModifiedTime.HasValue) {
request.Headers.Add("If-Unmodified-Since", expectedLastModifiedTime.Value.ToString("o", CultureInfo.InvariantCulture));
}
using (var response = await client.SendAsync(request))
{
await response.ThrowIfNotSuccessStatusCode();
return await response.Content.ReadAsAsync<Group>();
}
}
}

Expand Down
26 changes: 14 additions & 12 deletions src/Authress.SDK/Api/IAccessRecordsApi.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using Authress.SDK.DTO;

Expand All @@ -11,9 +12,9 @@ public interface IAccessRecordsApi
/// <summary>
/// Claim a resource by an allowed user Claim a resource by allowing a user to pick an identifier and receive admin access to that resource if it hasn't already been claimed. This only works for resources specifically marked as "CLAIM". The result will be a new access record listing that user as the admin for this resource.
/// </summary>
/// <param name="body"></param>
/// <param name="claimRequest"></param>
/// <returns>ClaimResponse</returns>
Task<ClaimResponse> CreateClaim (ClaimRequest body);
Task<ClaimResponse> CreateClaim (ClaimRequest claimRequest);

#region Access Requests
/// <summary>
Expand All @@ -27,9 +28,9 @@ public interface IAccessRecordsApi
/// <summary>
/// Create a new access record Specify user roles for specific resources.
/// </summary>
/// <param name="body"></param>
/// <param name="accessRecord"></param>
/// <returns>AccessRecord</returns>
Task<AccessRecord> CreateRecord (AccessRecord body);
Task<AccessRecord> CreateRecord (AccessRecord accessRecord);
/// <summary>
/// Deletes an access record. Remove an access record, removing associated permissions from all users in record. If a user has a permission from another record, that permission will not be removed.
/// </summary>
Expand All @@ -45,10 +46,11 @@ public interface IAccessRecordsApi
/// <summary>
/// Update an access record. Updates an access record adding or removing user permissions to resources.
/// </summary>
/// <param name="body"></param>
/// <param name="recordId">The identifier of the access record.</param>
/// /// <param name="accessRecord"></param>
/// /// <param name="expectedLastModifiedTime">The expected last time the record was updated.</param>
/// <returns>AccessRecord</returns>
Task<AccessRecord> UpdateRecord (string recordId, AccessRecord body);
Task<AccessRecord> UpdateRecord (string recordId, AccessRecord accessRecord, DateTimeOffset? expectedLastModifiedTime = null);
#endregion

#region Access Requests
Expand All @@ -58,9 +60,9 @@ public interface IAccessRecordsApi
/// <remarks>
/// Specify a request in the form of an access record that an admin can approve.
/// </remarks>
/// <param name="body"></param>
/// <param name="accessRecord"></param>
/// <returns>AccessRequest</returns>
Task<AccessRequest> CreateRequest (AccessRequest body);
Task<AccessRequest> CreateRequest (AccessRequest accessRecord);

/// <summary>
/// Deletes access request
Expand Down Expand Up @@ -101,9 +103,9 @@ public interface IAccessRecordsApi
/// Updates an access request, approving it or denying it.
/// </remarks>
/// <param name="requestId">The identifier of the access request.</param>
/// <param name="body"></param>
/// <param name="accessRecord"></param>
/// <returns>AccessRequest</returns>
Task<AccessRequest> RespondToAccessRequest (string requestId, AccessRequestResponse body);
Task<AccessRequest> RespondToAccessRequest (string requestId, AccessRequestResponse accessRecord);
#endregion

#region Invites
Expand All @@ -116,9 +118,9 @@ public interface IAccessRecordsApi
/// 5. This accepts the invite.
/// When the user accepts the invite they will automatically receive the permissions assigned in the Invite. Invites automatically expire after 7 days.
/// </summary>
/// <param name="body"></param>
/// <param name="accessRecord"></param>
/// <returns>Invite</returns>
Task<Invite> CreateInvite (Invite body);
Task<Invite> CreateInvite (Invite accessRecord);

/// <summary>
/// Accept invite
Expand Down
6 changes: 4 additions & 2 deletions src/Authress.SDK/Api/IGroupsApi.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Threading.Tasks;
using Authress.SDK.DTO;

Expand Down Expand Up @@ -38,8 +39,9 @@ public interface IGroupsApi
/// Update a group Updates a group adding or removing user. Change a group updates the permissions and roles the users have access to. (Groups have a maximum size of ~100KB)
/// </summary>
/// <param name="groupId">The identifier of the group.</param>
/// <param name="body"></param>
/// <param name="group"></param>
/// <param name="expectedLastModifiedTime">The expected last time the group was updated</param>
/// <returns>Group</returns>
Task<Group> UpdateGroup (string groupId, Group body);
Task<Group> UpdateGroup (string groupId, Group group, DateTimeOffset? expectedLastModifiedTime = null);
}
}
6 changes: 2 additions & 4 deletions src/Authress.SDK/Client/HttpClientProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,19 +176,17 @@ public RewriteBaseUrlHandler(HttpMessageHandler innerHandler, string baseUrl) :

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
var mergedPath = request.RequestUri.AbsolutePath.StartsWith(baseUrl.AbsolutePath) ? request.RequestUri.AbsolutePath : baseUrl.AbsolutePath + request.RequestUri.AbsolutePath.Substring(1);
var builder = new UriBuilder(baseUrl.Scheme, baseUrl.Host, baseUrl.Port)
{
Path = MergePath(baseUrl.AbsolutePath, request.RequestUri.AbsolutePath),
Path = mergedPath,
Query = request.RequestUri.Query,
Fragment = request.RequestUri.Fragment
};

request.RequestUri = builder.Uri;
return await base.SendAsync(request, cancellationToken);
}

private static string MergePath(string baseUrlPath, string requestPath) =>
requestPath.StartsWith(baseUrlPath) ? requestPath : baseUrlPath + requestPath.Substring(1);
}

internal class RetryHandler : DelegatingHandler
Expand Down

0 comments on commit 3962d05

Please sign in to comment.