Ignore:
Timestamp:
Feb 27, 2023, 9:40:12 PM (21 months ago)
Author:
alloc
Message:

Refactored API authorization to support per-HTTP-method permission levels

Location:
binary-improvements2/WebServer/src
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • binary-improvements2/WebServer/src/Permissions/AdminWebModules.cs

    r404 r418  
    4141
    4242                public void AddModule (string _module, int _permissionLevel) {
    43                         WebModule p = new WebModule (_module, _permissionLevel);
     43                        WebModule p = new WebModule (_module, _permissionLevel, false);
    4444                        lock (this) {
    4545                                allModulesList.Clear ();
     
    8484                        public readonly string Name;
    8585                        public readonly int PermissionLevel;
     86                        public readonly bool IsDefault;
    8687
    87                         public WebModule (string _name, int _permissionLevel) {
     88                        public WebModule (string _name, int _permissionLevel, bool _isDefault) {
    8889                                Name = _name;
    8990                                PermissionLevel = _permissionLevel;
     91                                IsDefault = _isDefault;
    9092                        }
    9193                       
     
    116118                                }
    117119                               
    118                                 _result = new WebModule (name, permissionLevel);
     120                                _result = new WebModule (name, permissionLevel, false);
    119121                                return true;
    120122                        }
     
    139141                        }
    140142
    141                         WebModule p = new WebModule (_module, _defaultPermission);
     143                        WebModule p = new WebModule (_module, _defaultPermission, true);
    142144
    143145                        lock (this) {
     
    158160
    159161                public bool ModuleAllowedWithLevel (string _module, int _level) {
    160                         WebModule permInfo = GetModule (_module);
     162                        WebModule permInfo = GetModule (_module)!.Value;
    161163                        return permInfo.PermissionLevel >= _level;
    162164                }
    163165
    164                 public WebModule GetModule (string _module) {
     166                public WebModule? GetModule (string _module, bool _returnDefaults = true) {
    165167                        if (modules.TryGetValue (_module, out WebModule result)) {
    166168                                return result;
     169                        }
     170
     171                        if (!_returnDefaults) {
     172                                return null;
    167173                        }
    168174
     
    170176                }
    171177
    172                 private readonly WebModule defaultModulePermission = new WebModule ("", 0);
     178                private readonly WebModule defaultModulePermission = new WebModule ("", 0, true);
    173179               
    174180#endregion
  • binary-improvements2/WebServer/src/UrlHandlers/ApiHandler.cs

    r404 r418  
    33using System.Net;
    44using System.Reflection;
    5 using Webserver.Permissions;
    65using Webserver.WebAPI;
    76
     
    4746                private void addApi (AbsWebAPI _api) {
    4847                        apis.Add (_api.Name, _api);
    49                         AdminWebModules.Instance.AddKnownModule ($"webapi.{_api.Name}", _api.DefaultPermissionLevel ());
    5048                }
    5149
     
    7169                        }
    7270
    73                         if (!IsAuthorizedForApi (apiName, _context.PermissionLevel)) {
     71                        _context.RequestPath = subPath;
     72
     73                        if (!api.Authorized (_context)) {
    7474                                _context.Response.StatusCode = (int) HttpStatusCode.Forbidden;
    7575                                if (_context.Connection != null) {
     
    7979                                return;
    8080                        }
    81 
    82                         _context.RequestPath = subPath;
    8381
    8482                        try {
     
    9290                        }
    9391                }
    94 
    95                 private bool IsAuthorizedForApi (string _apiName, int _permissionLevel) {
    96                         return AdminWebModules.Instance.ModuleAllowedWithLevel ($"webapi.{_apiName}", _permissionLevel);
    97                 }
    9892        }
    9993}
  • binary-improvements2/WebServer/src/WebAPI/AbsRestApi.cs

    r410 r418  
    44using System.Net;
    55using Utf8Json;
     6using Webserver.Permissions;
    67
    78namespace Webserver.WebAPI {
     
    910                private static readonly UnityEngine.Profiling.CustomSampler jsonDeserializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Deserialize");
    1011
     12                protected readonly string[] CachedPerMethodModuleNames = new string[(int)ERequestMethod.Count];
     13
    1114                protected AbsRestApi (string _name = null) : this(null, _name) {
    1215                }
    1316
    1417                protected AbsRestApi (Web _parentWeb, string _name = null) : base(_parentWeb, _name) {
     18                }
     19
     20                protected override void RegisterPermissions () {
     21                        base.RegisterPermissions ();
     22                       
     23                        for (int i = 0; i < (int)ERequestMethod.Count; i++) {
     24                                ERequestMethod method = (ERequestMethod)i;
     25
     26                                if (method is not (ERequestMethod.GET or ERequestMethod.PUT or ERequestMethod.POST or ERequestMethod.DELETE)) {
     27                                        continue;
     28                                }
     29
     30                                CachedPerMethodModuleNames [i] = $"webapi.{Name}:{method.ToStringCached ()}";
     31                                AdminWebModules.Instance.AddKnownModule (CachedPerMethodModuleNames [i], DefaultMethodPermissionLevel (method));
     32                        }
    1533                }
    1634
     
    4462
    4563                        try {
    46                                 switch (_context.Request.HttpMethod) {
    47                                         case "GET":
     64                                switch (_context.Method) {
     65                                        case ERequestMethod.GET:
    4866                                                if (inputJson != null) {
    4967                                                        SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "GET_WITH_BODY");
     
    5371                                                HandleRestGet (_context);
    5472                                                return;
    55                                         case "POST":
     73                                        case ERequestMethod.POST:
    5674                                                if (!string.IsNullOrEmpty (_context.RequestPath)) {
    5775                                                        SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "POST_WITH_ID");
     
    6684                                                HandleRestPost (_context, inputJson, jsonInputData);
    6785                                                return;
    68                                         case "PUT":
     86                                        case ERequestMethod.PUT:
    6987                                                if (string.IsNullOrEmpty (_context.RequestPath)) {
    7088                                                        SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "PUT_WITHOUT_ID");
     
    7997                                                HandleRestPut (_context, inputJson, jsonInputData);
    8098                                                return;
    81                                         case "DELETE":
     99                                        case ERequestMethod.DELETE:
    82100                                                if (string.IsNullOrEmpty (_context.RequestPath)) {
    83101                                                        SendErrorResult (_context, HttpStatusCode.BadRequest, jsonInputData, "DELETE_WITHOUT_ID");
     
    101119                }
    102120
     121                protected virtual void HandleRestGet (RequestContext _context) {
     122                        SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported");
     123                }
     124
     125                protected virtual void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
     126                        SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported");
     127                }
     128
     129                protected virtual void HandleRestPut (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
     130                        SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported");
     131                }
     132
     133                protected virtual void HandleRestDelete (RequestContext _context) {
     134                        SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported");
     135                }
     136
     137                public override bool Authorized (RequestContext _context) {
     138                        return ActiveMethodPermissionLevel (_context.Method) >= _context.PermissionLevel;
     139                }
     140
     141                /// <summary>
     142                /// Define default permission levels per HTTP method
     143                /// </summary>
     144                /// <param name="_method">HTTP method to return the default value for</param>
     145                /// <returns>Default permission level for the given HTTP method. A value of int.MinValue means no per-method default, use per-API default</returns>
     146                public virtual int DefaultMethodPermissionLevel (ERequestMethod _method) => int.MinValue;
     147
     148                public virtual int ActiveMethodPermissionLevel (ERequestMethod _method) {
     149                        string methodApiModuleName = CachedPerMethodModuleNames [(int)_method];
     150
     151                        if (methodApiModuleName == null) {
     152                                return 0;
     153                        }
     154
     155                        AdminWebModules.WebModule? overrideModule = AdminWebModules.Instance.GetModule (methodApiModuleName, false);
     156                        if (overrideModule.HasValue) {
     157                                return overrideModule.Value.PermissionLevel;
     158                        }
     159
     160                        overrideModule = AdminWebModules.Instance.GetModule (CachedApiModuleName, false);
     161                        if (overrideModule.HasValue) {
     162                                return overrideModule.Value.PermissionLevel;
     163                        }
     164
     165                        int defaultMethodPermissionLevel = DefaultMethodPermissionLevel (_method);
     166                        // ReSharper disable once ConvertIfStatementToReturnStatement
     167                        if (defaultMethodPermissionLevel != int.MinValue) {
     168                                return defaultMethodPermissionLevel;
     169                        }
     170
     171                        return DefaultPermissionLevel ();
     172                }
     173
     174#region Helpers
     175
     176                protected static readonly byte[] JsonEmptyData;
     177               
    103178                static AbsRestApi () {
    104179                        JsonWriter writer = new JsonWriter ();
     
    108183                }
    109184
    110                 protected virtual void HandleRestGet (RequestContext _context) {
    111                         SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported");
    112                 }
    113 
    114                 protected virtual void HandleRestPost (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
    115                         SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported");
    116                 }
    117 
    118                 protected virtual void HandleRestPut (RequestContext _context, IDictionary<string, object> _jsonInput, byte[] _jsonInputData) {
    119                         SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, _jsonInputData, "Unsupported");
    120                 }
    121 
    122                 protected virtual void HandleRestDelete (RequestContext _context) {
    123                         SendErrorResult (_context, HttpStatusCode.MethodNotAllowed, null, "Unsupported");
    124                 }
    125 
    126 
    127 #region Helpers
    128 
    129                 protected static readonly byte[] JsonEmptyData;
    130                
    131185                protected static void PrepareEnvelopedResult (out JsonWriter _writer) {
    132186                        WebUtils.PrepareEnvelopedResult (out _writer);
  • binary-improvements2/WebServer/src/WebAPI/AbsWebAPI.cs

    r410 r418  
     1using Webserver.Permissions;
     2
    13namespace Webserver.WebAPI {
    24        public abstract class AbsWebAPI {
    35                public readonly string Name;
    46                protected readonly Web ParentWeb;
     7
     8                protected readonly string CachedApiModuleName;
    59
    610                protected AbsWebAPI (string _name = null) : this(null, _name) {
     
    1014                        Name = _name ?? GetType ().Name;
    1115                        ParentWeb = _parentWeb;
     16                        CachedApiModuleName = $"webapi.{Name}";
     17                        RegisterPermissions ();
     18                }
     19
     20                protected virtual void RegisterPermissions () {
     21                        AdminWebModules.Instance.AddKnownModule ($"webapi.{Name}", DefaultPermissionLevel ());
    1222                }
    1323
    1424                public abstract void HandleRequest (RequestContext _context);
    1525
     26                public virtual bool Authorized (RequestContext _context) {
     27                        return AdminWebModules.Instance.ModuleAllowedWithLevel (CachedApiModuleName, _context.PermissionLevel);
     28                }
     29
    1630                public virtual int DefaultPermissionLevel () => 0;
    1731        }
Note: See TracChangeset for help on using the changeset viewer.