Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

basic start to adding default acl support #822

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System.Collections.Generic;
using System.Linq;
using System.Security.AccessControl;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Odin.Core;
using Odin.Hosting.Controllers.Base;
using Odin.Services.Authentication.Owner;
using Odin.Services.Authorization.Acl;
using Odin.Services.Base;
using Odin.Services.Base.SharedTypes;
using Odin.Services.Drives;
Expand Down Expand Up @@ -70,6 +72,14 @@ public async Task<bool> UpdateDriveMetadata([FromBody] UpdateDriveDefinitionRequ
return true;
}

[HttpPost("update-default-read-acl")]
public async Task<bool> UpdateDefaultReadAclAsync([FromBody] UpdateDriveDefinitionRequest request)
{
var driveId = await _driveManager.GetDriveIdByAliasAsync(request.TargetDrive, true);
await _driveManager.UpdateDefaultReadAclAsync(driveId.GetValueOrDefault(), request.DefaultReadAcl, WebOdinContext);
return true;
}

[HttpPost("UpdateAttributes")]
public async Task<bool> UpdateDriveAttributes([FromBody] UpdateDriveDefinitionRequest request)
{
Expand Down Expand Up @@ -119,6 +129,8 @@ public class UpdateDriveDefinitionRequest
public string Metadata { get; set; }

public Dictionary<string,string> Attributes { get; set; }

public AccessControlList DefaultReadAcl { get; set; }
}

public class UpdateDriveReadModeRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,39 @@
using Odin.Core.Exceptions;
using Odin.Core.Identity;
using Odin.Services.Base;
using Odin.Services.Drives.Management;
using Odin.Services.Membership.Connections;

namespace Odin.Services.Authorization.Acl
{
public class DriveAclAuthorizationService(
CircleNetworkService circleNetwork,
DriveManager driveManager,
ILogger<DriveAclAuthorizationService> logger)
: IDriveAclAuthorizationService
{
public async Task AssertCallerHasPermission(AccessControlList acl, IOdinContext odinContext)
public async Task AssertCallerMatchesAclAsync(Guid driveId, AccessControlList acl, IOdinContext odinContext)
{
ThrowWhenFalse(await CallerHasPermission(acl, odinContext));
ThrowWhenFalse(await CallerMatchesAclAsync(driveId, acl, odinContext));
}

public async Task<bool> IdentityHasPermissionAsync(OdinId odinId, AccessControlList acl, IOdinContext odinContext)
public async Task<bool> IdentityMatchesAclAsync(Guid driveId, OdinId odinId, AccessControlList acl, IOdinContext odinContext)
{
//there must be an acl
if (acl == null)
var appliedAcl = acl;

if (appliedAcl == null)
{
return false;
appliedAcl = (await driveManager.GetDriveAsync(driveId)).DefaultReadAcl;

//there must be an acl
if (appliedAcl == null)
{
return false;
}
}

//if file has required circles, see if caller has at least one
var requiredCircles = acl.GetRequiredCircles().ToList();
var requiredCircles = appliedAcl.GetRequiredCircles().ToList();
if (requiredCircles.Any())
{
var icr = await circleNetwork.GetIcrAsync(odinId, odinContext, true);
Expand All @@ -41,17 +50,18 @@ public async Task<bool> IdentityHasPermissionAsync(OdinId odinId, AccessControlL
//let it continue on
}

var hasAtLeastOneCircle = requiredCircles.Intersect(icr.AccessGrant.CircleGrants?.Select(cg => cg.Value.CircleId.Value) ?? Array.Empty<Guid>())
var hasAtLeastOneCircle = requiredCircles
.Intersect(icr.AccessGrant.CircleGrants?.Select(cg => cg.Value.CircleId.Value) ?? Array.Empty<Guid>())
.Any();
return hasAtLeastOneCircle;
}

if (acl.GetRequiredIdentities().Any())
if (appliedAcl.GetRequiredIdentities().Any())
{
return false;
}

switch (acl.RequiredSecurityGroup)
switch (appliedAcl.RequiredSecurityGroup)
{
case SecurityGroupType.Anonymous:
return true;
Expand All @@ -63,51 +73,57 @@ public async Task<bool> IdentityHasPermissionAsync(OdinId odinId, AccessControlL
return false;
}

public Task<bool> CallerHasPermission(AccessControlList acl, IOdinContext odinContext)
public async Task<bool> CallerMatchesAclAsync(Guid driveId, AccessControlList acl, IOdinContext odinContext)
{
var caller = odinContext.Caller;
if (caller?.IsOwner ?? false)
{
return Task.FromResult(true);
return true;
}

if (caller?.SecurityLevel == SecurityGroupType.System)
{
return Task.FromResult(true);
return true;
}

//there must be an acl
if (acl == null)
var appliedAcl = acl;
if (appliedAcl == null)
{
return Task.FromResult(false);
appliedAcl = (await driveManager.GetDriveAsync(driveId)).DefaultReadAcl;

//there must be an acl
if (appliedAcl == null)
{
return false;
}
}

//if file has required circles, see if caller has at least one
var requiredCircles = acl.GetRequiredCircles().ToList();
var requiredCircles = appliedAcl.GetRequiredCircles().ToList();
if (requiredCircles.Any() && !requiredCircles.Intersect(caller!.Circles.Select(c => c.Value)).Any())
{
return Task.FromResult(false);
return false;
}

if (acl.GetRequiredIdentities().Any())
if (appliedAcl.GetRequiredIdentities().Any())
{
throw new NotImplementedException("TODO: enforce logic for required identities");
}

switch (acl.RequiredSecurityGroup)
switch (appliedAcl.RequiredSecurityGroup)
{
case SecurityGroupType.Anonymous:
return Task.FromResult(true);
return true;

case SecurityGroupType.Authenticated:
return Task.FromResult(((int)caller!.SecurityLevel) >= (int)SecurityGroupType.Authenticated);
return ((int)caller!.SecurityLevel) >= (int)SecurityGroupType.Authenticated;

case SecurityGroupType.AutoConnected:
case SecurityGroupType.Connected:
return CallerIsConnected(odinContext);
return await CallerIsConnected(odinContext);
}

return Task.FromResult(false);
return false;
}

private void ThrowWhenFalse(bool eval)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
using System.Collections.Generic;
using System;
using System.Threading.Tasks;
using Odin.Core.Identity;
using Odin.Core.Storage.SQLite;
using Odin.Services.Base;

namespace Odin.Services.Authorization.Acl
{
public interface IDriveAclAuthorizationService
{
Task<bool> IdentityHasPermissionAsync(OdinId odinId, AccessControlList acl, IOdinContext odinContext);
Task<bool> IdentityMatchesAclAsync(Guid driveId,OdinId odinId, AccessControlList acl, IOdinContext odinContext);

Task AssertCallerHasPermission(AccessControlList acl, IOdinContext odinContext);
Task AssertCallerMatchesAclAsync(Guid driveId, AccessControlList acl, IOdinContext odinContext);

Task<bool> CallerHasPermission(AccessControlList acl, IOdinContext odinContext);
Task<bool> CallerMatchesAclAsync(Guid driveId, AccessControlList appliedAcl, IOdinContext odinContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,8 @@ private async Task SendFileOverTransit(ServerFileHeader header, List<OdinId> rec
else
{
// this should not happen
_logger.LogError("No transfer status found for recipient [{recipient}] for fileId [{fileId}] on [{drive}]", recipient, file.FileId,
_logger.LogError("No transfer status found for recipient [{recipient}] for fileId [{fileId}] on [{drive}]", recipient,
file.FileId,
file.DriveId);
}
}
Expand Down Expand Up @@ -374,10 +375,11 @@ private async Task<List<OdinId>> GetConnectedFollowersWithFilePermissionAsync(ID
{
return [];
}

// find all followers that are connected, return those which are not to be processed differently
var connectedIdentities = await _circleNetworkService.GetCircleMembersAsync(SystemCircleConstants.ConfirmedConnectionsCircleId, odinContext);

var connectedIdentities =
await _circleNetworkService.GetCircleMembersAsync(SystemCircleConstants.ConfirmedConnectionsCircleId, odinContext);

// NOTE!
//
// ChatGPT has refactored the original code below to run asynchronously.
Expand All @@ -390,7 +392,7 @@ private async Task<List<OdinId>> GetConnectedFollowersWithFilePermissionAsync(ID
// db)
// .GetAwaiter().GetResult()).ToList();
// return connectedFollowers;

//
// ChatGPT from here:
//
Expand All @@ -402,7 +404,8 @@ private async Task<List<OdinId>> GetConnectedFollowersWithFilePermissionAsync(ID
var permissionTasks = intersectedFollowers.Select(async follower => new
{
OdinId = (OdinId)follower.DomainName,
HasPermission = await _driveAcl.IdentityHasPermissionAsync(
HasPermission = await _driveAcl.IdentityMatchesAclAsync(
notification.ServerFileHeader.FileMetadata.File.DriveId,
(OdinId)follower.DomainName,
notification.ServerFileHeader.ServerMetadata.AccessControlList,
odinContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ await mediator.Publish(new DriveFileChangedNotification
/// Writes a new file header w/o checking for an existing one
/// </summary>
public async Task WriteNewFileHeader(InternalDriveFileId targetFile, ServerFileHeader header, IOdinContext odinContext,

bool raiseEvent = false)
{
if (!header.IsValid())
Expand Down Expand Up @@ -239,7 +238,8 @@ public async Task DeleteTempFiles(InternalDriveFileId file, IOdinContext odinCon
await tempStorageManager.EnsureDeleted(drive, file.FileId);
}

public async Task<(Stream stream, ThumbnailDescriptor thumbnail)> GetThumbnailPayloadStreamAsync(InternalDriveFileId file, int width,
public async Task<(Stream stream, ThumbnailDescriptor thumbnail)> GetThumbnailPayloadStreamAsync(InternalDriveFileId file,
int width,
int height,
string payloadKey, UnixTimeUtcUnique payloadUid, IOdinContext odinContext, bool directMatchOnly = false)
{
Expand Down Expand Up @@ -364,7 +364,8 @@ public async Task<bool> CallerHasPermissionToFile(InternalDriveFileId file, IOdi
return false;
}

return await driveAclAuthorizationService.CallerHasPermission(header.ServerMetadata.AccessControlList, odinContext);
return await driveAclAuthorizationService.CallerMatchesAclAsync(file.DriveId, header.ServerMetadata.AccessControlList,
odinContext);
}

public async Task<ServerFileHeader> GetServerFileHeader(InternalDriveFileId file, IOdinContext odinContext)
Expand Down Expand Up @@ -412,7 +413,8 @@ public async Task<FileSystemType> ResolveFileSystemType(InternalDriveFileId file
return header.ServerMetadata.FileSystemType;
}

public async Task<PayloadStream> GetPayloadStreamAsync(InternalDriveFileId file, string key, FileChunk chunk, IOdinContext odinContext)
public async Task<PayloadStream> GetPayloadStreamAsync(InternalDriveFileId file, string key, FileChunk chunk,
IOdinContext odinContext)
{
await AssertCanReadDriveAsync(file.DriveId, odinContext);
DriveFileUtility.AssertValidPayloadKey(key);
Expand Down Expand Up @@ -515,7 +517,8 @@ public async Task CommitNewFile(InternalDriveFileId targetFile, KeyHeader keyHea
var extension = DriveFileUtility.GetThumbnailFileExtension(descriptor.Key, descriptor.Uid, thumb.PixelWidth,
thumb.PixelHeight);
var sourceThumbnail = await tempStorageManager.GetPath(drive, targetFile.FileId, extension);
await longTermStorageManager.MoveThumbnailToLongTermAsync(drive, targetFile.FileId, sourceThumbnail, descriptor, thumb);
await longTermStorageManager.MoveThumbnailToLongTermAsync(drive, targetFile.FileId, sourceThumbnail, descriptor,
thumb);
}
}
}
Expand Down Expand Up @@ -604,7 +607,8 @@ public async Task OverwriteFile(InternalDriveFileId tempFile, InternalDriveFileI
var extension = DriveFileUtility.GetThumbnailFileExtension(descriptor.Key, descriptor.Uid, thumb.PixelWidth,
thumb.PixelHeight);
var sourceThumbnail = await tempStorageManager.GetPath(drive, tempFile.FileId, extension);
await longTermStorageManager.MoveThumbnailToLongTermAsync(drive, targetFile.FileId, sourceThumbnail, descriptor, thumb);
await longTermStorageManager.MoveThumbnailToLongTermAsync(drive, targetFile.FileId, sourceThumbnail, descriptor,
thumb);
}
}
}
Expand Down Expand Up @@ -848,7 +852,6 @@ public async Task WriteNewFileToFeedDriveAsync(KeyHeader keyHeader, FileMetadata
}

public async Task ReplaceFileMetadataOnFeedDrive(InternalDriveFileId file, FileMetadata fileMetadata, IOdinContext odinContext,

bool bypassCallerCheck = false)
{
await AssertCanWriteToDrive(file.DriveId, odinContext);
Expand Down Expand Up @@ -963,7 +966,6 @@ await mediator.Publish(new ReactionPreviewUpdatedNotification
}

public async Task UpdateActiveFileHeader(InternalDriveFileId targetFile, ServerFileHeader header, IOdinContext odinContext,

bool raiseEvent = false)
{
await UpdateActiveFileHeaderInternal(targetFile, header, false, odinContext, raiseEvent);
Expand Down Expand Up @@ -1030,7 +1032,8 @@ public async Task UpdateBatchAsync(InternalDriveFileId tempFile, InternalDriveFi
var extension = DriveFileUtility.GetThumbnailFileExtension(newDescriptor.Key, newDescriptor.Uid, thumb.PixelWidth,
thumb.PixelHeight);
var sourceThumbnail = await tempStorageManager.GetPath(drive, tempFile.FileId, extension);
await longTermStorageManager.MoveThumbnailToLongTermAsync(drive, targetFile.FileId, sourceThumbnail, newDescriptor, thumb);
await longTermStorageManager.MoveThumbnailToLongTermAsync(drive, targetFile.FileId, sourceThumbnail, newDescriptor,
thumb);
}

//
Expand Down Expand Up @@ -1081,6 +1084,7 @@ await mediator.Publish(new DriveFileChangedNotification
//);
}


private async Task WriteFileHeaderInternal(ServerFileHeader header, bool keepSameVersionTag = false)
{
if (!keepSameVersionTag)
Expand Down Expand Up @@ -1189,7 +1193,8 @@ private async Task<ServerFileHeader> GetServerFileHeaderInternal(InternalDriveFi
return null;
}

await driveAclAuthorizationService.AssertCallerHasPermission(header.ServerMetadata.AccessControlList, odinContext);
await driveAclAuthorizationService.AssertCallerMatchesAclAsync(file.DriveId, header.ServerMetadata.AccessControlList,
odinContext);

return header;
}
Expand Down Expand Up @@ -1272,7 +1277,8 @@ private async Task DeletePayloadFromDiskInternal(InternalDriveFileId file, Paylo
// Delete the thumbnail files for this payload
foreach (var thumb in descriptor.Thumbnails ?? new List<ThumbnailDescriptor>())
{
await longTermStorageManager.DeleteThumbnailFile(drive, file.FileId, descriptor.Key, descriptor.Uid, thumb.PixelWidth, thumb.PixelHeight);
await longTermStorageManager.DeleteThumbnailFile(drive, file.FileId, descriptor.Key, descriptor.Uid, thumb.PixelWidth,
thumb.PixelHeight);
}

// Delete the payload file
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using Odin.Services.Authorization.Acl;

namespace Odin.Services.Drives.Management;

Expand All @@ -12,5 +13,7 @@ public class CreateDriveRequest
public bool AllowSubscriptions { get; set; }
public bool OwnerOnly { get; set; }

public AccessControlList DefaultReadAcl { get; set; }

public Dictionary<string, string> Attributes { get; set; }
}
Loading
Loading