How to programmatically assign roles and permissions to services and/or RequestDTOs - servicestack

Statically I set access to my services like so:
[Authenticate]
public class AppUserService : Service
{
[RequiredRole("Administrator")]
public object Post(CreateAppUser request)
{
//.....
}
}
How can I do this programmatically?
I will let the user create roles using a GUI. Then I will present a list of available methods, e.g. by providing the methods using improved code like:
var appHost = HostContext.AppHost;
var restPaths = appHost.RestPaths;
foreach (var restPath in restPaths)
{
var reqType = restPath.RequestType;
string verbs = string.Empty;
if (restPath.Verbs != null)
{
var counter = 0;
foreach (var verb in restPath.Verbs)
{
if (counter > 0) verbs += ", ";
verbs += verb;
counter++;
}
}
Debug.WriteLine($"Path: {restPath.Path} | Verbs: {verbs} | Name: {reqType.Name} FullName: {reqType.FullName}");
}
The code above outputs something like
Path: /appusers | Verbs: POST | Name: CreateAppUser FullName: MyServer.ServiceModel.DTOs.Request.CreateAppUser
So I could show in my UI the Name property of the RequestType and let him define, what roles are allowed to call this method. So the user may create a role called 'User Management' and allow members of this role to execute CreateAppUser.
Using annotations I would write
[RequiredRole("Administrator", "User Management")]
public object Post(CreateAppUser request)
{ .... }
Is this anyhow possible in C# code?

ServiceStack does have a way to dynamically Add Attributes at runtime, e.g:
typeof(CreateAppUser)
.AddAttributes(new RequiredRoleAttribute("Administrator", "User Management"));
Which is an alternative to declaring attributes statically, but it's still not a solution for a data-driven authorization system.
But you could add your own custom Authorization logic in addition to ServiceStack's built-in attributes by validating it in your Service:
public ICustomAuth CustomAuth { get; set; }
public object Post(CreateAppUser request)
{
var session = base.GetSession();
var requiresRoles = CustomAuth.GetRequiredRoles(request.GetType());
var hasAllRoles = requiresRoles.All(r =>
session.HasRole(r, base.AuthRepository))
if (!hasAllRoles)
throw new UnauthorizedAccessException("Requires all roles");
}
If you do this a lot you will want to refactor the custom validation logic into a single reusable method, or if you prefer into a custom RequestFilter attribute.

Related

Servicestack automap update endpoints

We are using ServiceStack QueryDb to expose certain business objects for auto-querying, and it is working great.
[Route("/catalog/customers")]
[Authenticate]
public class QueryCustomers : QueryDb<ServiceModel.Catalog.Customer> { }
We'd like to implement some sort of UpdateDb that auto-maps authenticated POST, PUT and DELETE requests to insert, update and delete auto-mapped predefined OrmLite business objects, but don't see any examples on the web. Ideally, we could create an endpoint as simple as:
[Route("/edits/customers")]
[Authenticate]
public class UpdateCustomers : UpdateDb<ServiceModel.Catalog.Customer> { }
I'd prefer to not have to roll our own if this has already been done somewhere in the ServiceStack library or elsewhere... Is this something that ServiceStack already supports, or are we on our own in developing this UpdateDb utility?...
This would require a CRUD version of AutoQuery which doesn't exist yet so you would need to implement your Update services as normal.
Haven't done a lot of testing, but it looks like the UpdateDb will involve one tweak against the QueryDb implementation -
public long CreateUpdate<From>(IUpdateDb<From> dto, Dictionary<string, string> dynamicParams, IRequest req = null)
{
long result;
var db = GetDb<From>(req);
var body = req.GetRawBody();
var jsonObject = JsonObject.Parse(body);
var obj = jsonObject.ConvertTo<From>();
var id = (obj as IHasId<int>)?.Id;
if (id == null)
throw new Exception("Cannot update without identity field defined");
if (req.Verb == "DELETE")
{
result = db.DeleteById<From>(id);
}
else if (req.Verb == "POST")
{
db.InsertAll(new[] { obj });
result = ((IHasId<int>)obj).Id;
}
else
{
result = db.UpdateOnly(obj,
jsonObject.Keys.ToArray(),
u => ((IHasId<int>)u).Id == id);
}
return result;
}

Servicestack Roles Users and Groups

Since roles don't contain permissions. I am a bit confused by the Roles and Permission in ServiceStack. It appears they are really the same thing? I want to implement a Group, that has roles, that has permissions. Based on the servicestack default implementation I don't think I can extend the provider and get the nested information.
How would i achieve this and still use the authentication attributes.
If i had an attribute
[RequiredPermission("CanAccessPerm")]
That is in Role:HasAccessRole That is in Group:HasAccessGroup
I would want to only use perms to determine access at the API level. Then Roles and Groups to determine who has perms. If Roles contained permissions then I could just extend the CredentialsAuthProvider TryAuthenticate and additionally look at a group table. Is there a way to do this and not rewrite the whole authentication?
Edit 12/12
I am using
container.Register(c =>
new OrmLiteAuthRepository(c.Resolve())
{
UseDistinctRoleTables = AppSettings.Get("UseDistinctRoleTables", true),
});
How do I get to the IManage roles? I see i can override the IAuthRepository.
I found this link. But its not a replacement for Auth
ServiceStack - roles and permissions
-_Edit 12/29 -- It is not calling the methods in MyOrmLiteAuthRepository. Do you know why?
AppHost.cs
container.Register<IAuthRepository>(c =>
new MyOrmLiteAuthRepository(c.Resolve<IDbConnectionFactory>())
{
UseDistinctRoleTables = AppSettings.Get("UseDistinctRoleTables", true),
});
CustomAuthRepo
public class MyOrmLiteAuthRepository : OrmLiteAuthRepository
{
public MyOrmLiteAuthRepository(IDbConnectionFactory dbFactory) : base(dbFactory) { }
public MyOrmLiteAuthRepository(IDbConnectionFactory dbFactory, string namedConnnection = null)
: base(dbFactory, namedConnnection)
{
DbFactory = dbFactory;
NamedConnnection = namedConnnection;
}
public IDbConnectionFactory DbFactory { get; set; }
public string NamedConnnection { get; set; }
public override ICollection<string> GetPermissions(string userAuthId)
{
var permissions = base.GetPermissions(userAuthId);
using (var ss = HostContext.ResolveService<SecurityService>(new BasicRequest()))
{
permissions = ss.UserPermissions(Convert.ToInt32(userAuthId));
}
return permissions;
}
public override bool HasPermission(string userAuthId, string permission)
{
var hasPermission = base.HasPermission(userAuthId, permission);
using (var ss = HostContext.ResolveService<SecurityService>(new BasicRequest()))
{
hasPermission = ss.UserHasPermInRoleOrGroup(permission, Convert.ToInt32(userAuthId));
}
return hasPermission;
}
}
Roles/Permissions work similar in that a User can have multiple Roles and Permissions but they're logically different in that a Role defines the Role a person has like "Employee", "Manager", etc and Permission defines functionality they have access to like "CanSubmitPurchaseOrders", "CanRefundCustomers", etc.
ServiceStack doesn't support Roles having permissions themselves but you can implement this functionality yourself in your own Custom AuthProvider by overriding OnAuthenticated() and populating the Permissions collections of AuthUserSession with a combination of all the permissions in all the Roles a User is in. If you're not using a custom AuthProvider you can modify the Users Session by implementing the OnAuthenticated() Session of Auth Event Hooks.
Alternatively if you're using an AuthRepository like OrmLiteAuthRepository you can change how permissions are managed by overriding its IManageRoles GetPermissions() and HasPermission() APIs to also inspect the Permissions that the Users Roles have assigned to them which you would need to maintain in an out-of-band table.
Overriding OrmLiteAuthRepository
OrmLiteAuthRepository implements IManageRoles so when needed you can cast IAuthRepository to IManageRoles, e.g:
var manageRoles = (IManageRoles)container.Resolve<IAuthRepository>();
You can override OrmLiteAuthRepository and implement your own GetPermissions() and HasPermission() with normal inheritance, e.g:
public class MyOrmLiteAuthRepository : OrmLiteAuthRepository
{
public MyOrmLiteAuthRepository(IDbConnectionFactory dbFactory) : base(dbFactory) { }
public MyOrmLiteAuthRepository(IDbConnectionFactory dbFactory, string namedConnnection = null)
: base(dbFactory, namedConnnection) {}
public override ICollection<string> GetPermissions(string userAuthId)
{
return base.GetPermissions(userAuthId);
}
public override bool HasPermission(string userAuthId, string permission)
{
return base.HasPermission(userAuthId, permission);
}
}

Unable to use multiple instances of MobileServiceClient concurrently

I structured my project into multiple mobile services, grouped by the application type eg:
my-core.azure-mobile.net (user, device)
my-app-A.azure-mobile.net (sales, order, invoice)
my-app-B.azure-mobile.net (inventory & parts)
I'm using custom authentication for all my services, and I implemented my own SSO by setting the same master key to all 3 services.
Things went well when I tested using REST client, eg. user who "logged in" via custom api at my-core.azure-mobile.net is able to use the returned JWT token to access restricted API of the other mobile services.
However, in my xamarin project, only the first (note, in sequence of creation) MobileServiceClient object is working properly (eg. returning results from given table). The client object are created using their own url and key respectively, and stored in a dictionary.
If i created client object for app-A then only create for app-B, I will be able to perform CRUD+Sync on sales/order/invoice entity, while CRUD+Sync operation on inventory/part entity will just hang there. The situation is inverse if I swap the client object creation order.
I wonder if there is any internal static variables used within the MobileServiceClient which caused such behavior, or it is a valid bug ?
=== code snippet ===
public class AzureService
{
IDictionary<String, MobileServiceClient> services = new Dictionary<String, MobileServiceClient>();
public MobileServiceClient Init (String key, String applicationURL, String applicationKey)
{
return services[key] = new MobileServiceClient (applicationURL, applicationKey);
}
public MobileServiceClient Get(String key)
{
return services [key];
}
public void InitSyncContext(MobileServiceSQLiteStore offlineStore)
{
// Uses the default conflict handler, which fails on conflict
// To use a different conflict handler, pass a parameter to InitializeAsync.
// For more details, see http://go.microsoft.com/fwlink/?LinkId=521416
var syncHandler = new MobileServiceSyncHandler ();
foreach(var client in services) {
client.Value.SyncContext.InitializeAsync (offlineStore, syncHandler);
}
}
public void SetAuthenticationToken(String uid, String token)
{
var user = new MobileServiceUser(uid);
foreach(var client in services) {
client.Value.CurrentUser = user;
client.Value.CurrentUser.MobileServiceAuthenticationToken = token;
}
}
public void ClearAuthenticationToken()
{
foreach(var client in services) {
client.Value.CurrentUser = null;
}
}
}
=== more code ===
public class DatabaseService
{
public static MobileServiceSQLiteStore LocalStore = null;
public static string Path { get; set; }
public static ISet<IEntityMappingProvider> Providers = new HashSet<IEntityMappingProvider> ();
public static void Init (String dbPath)
{
LocalStore = new MobileServiceSQLiteStore(dbPath);
foreach(var provider in Providers) {
var types = provider.GetSupportedTypes ();
foreach(var t in types) {
JObject item = null;
// omitted detail to create JObject using reflection on given type
LocalStore.DefineTable(tableName, item);
}
}
}
}
=== still code ===
public class AzureDataSyncService<T> : IAzureDataSyncService<T>
{
public MobileServiceClient ServiceClient { get; set; }
public virtual Task<List<T>> GetAll()
{
try
{
var theTable = ServiceClient.GetSyncTable<T>();
return theTable.ToListAsync();
}
catch (MobileServiceInvalidOperationException msioe)
{
Debug.WriteLine("GetAll<{0}> EXCEPTION TYPE: {1}, EXCEPTION:{2}", typeof(T).ToString(), msioe.GetType().ToString(), msioe.ToString());
}
catch (Exception e)
{
Debug.WriteLine("GetAll<{0}> EXCEPTION TYPE: {1}, EXCEPTION:{2}", typeof(T).ToString(), e.GetType().ToString(), e.ToString());
}
List<T> theCollection = Enumerable.Empty<T>().ToList();
return Task.FromResult(theCollection);
}
}
=== code ===
public class UserService : AzureDataSyncService<User>
{
}
public class PartService : AzureDataSyncService<Part>
{
}
const string coreApiURL = #"https://my-core.azure-mobile.net/";
const string coreApiKey = #"XXXXX";
const string invApiURL = #"https://my-inventory.azure-mobile.net/";
const string invApiKey = #"YYYYY";
public async void Foo ()
{
DatabaseService.Providers.Add (new CoreDataMapper());
DatabaseService.Providers.Add (new InvDataMapper ());
DatabaseService.Init (DatabaseService.Path);
var coreSvc = AzureService.Instance.Init ("Core", coreApiURL, coreApiKey);
var invSvc = AzureService.Instance.Init ("Inv", invApiURL, invApiKey);
AzureService.Instance.InitSyncContext (DatabaseService.LocalStore);
AzureService.Instance.SetAuthenticationToken("AAA", "BBB");
UserService.Instance.ServiceClient = coreSvc;
PartService.Instance.ServiceClient = invSvc;
var x = await UserService.GetAll(); // this will work
var y = await PartService.GetAll(); // but not this
}
It's ok to use multiple MobileServiceClient objects, but not with the same local database. The offline sync feature uses a particular system tables to keep track of table operations and errors, and it is not supported to use the same local store across multiple sync contexts.
I'm not totally sure why it is hanging in your test, but it's possible that there is a lock on the local database file and the other sync context is waiting to get access.
You should instead use different local database files for each service and doing push and pull on each sync context. With your particular example, you just need to move LocalStore out of DatabaseService and into a dictionary in AzureService.
In general, it seems like an unusual design to use multiple services from the same client app. Is there a particular reason that the services need to be separated from each other?

Get Resource based on currently authenticated user

If I have an operation using ServiceStack such as GetOrders:
[Route("/orders")]
public class GetOrders : IReturn<List<Order>> { }
I then use this in a service:
[Authenticate]
public class OrdersService : Service
{
public object Get(GetOrders request)
{
var dbOrders = Db.Select<Order>().ToList();
// What I want is to only get orders of the user making the request
// var dbOrders = Db.Select<Order>().Where(x=>x.UserId == ??).ToList();
return dbOrders;
}
}
Assuming my Order entity has a property called UserId, how do I get access to the currently logged in user where I can then map to the UserId and select only those orders from my database?
You can get access to your typed UserSession via the SessionAs<T> method, e.g:
[Authenticate]
public class OrdersService : Service
{
public object Get(GetOrders request)
{
var userSession = base.SessionAs<AuthUserSession>();
var userId = int.Parse(userSession.UserAuthId);
var dbOrders = Db.Select<Order>(x => x.UserId == userId);
return dbOrders;
}
}

Variable Placeholder Ignored

I setup two routes for my service:
GET /foo
GET /foo/{Name}
The metadata page correctly lists:
GET /foo
GET /foo/{Name}
But when I browse to /baseurl/foo/NameValueHere I get
The operation 'NameValueHere' does not exist for this service
Am I missing some configuration in my (self-hosted) apphost?
Edit:
Additional information
I didn't like the Route attribute over my DTOs, so I wrote a simple extension method for IServiceRoutes.
public static IServiceRoutes AddFromServiceAttributes(this IServiceRoutes routes)
{
var myServiceTypes = typeof(MyServiceBase).Assembly.GetDerivedTypesOf<MyServiceBase>();
var routedMethods = myServiceTypes.SelectMany(type => type.GetMethodsWithAttribute<MyServiceRouteAttribute>());
foreach (var routedMethod in routedMethods)
{
var routesFound = routedMethod.GetAttributes<MyServiceRouteAttribute>();
foreach (var route in routesFound)
{
// request type can be inferred from the method's first param
// and the same for allowed verbs from the method's name
// [MyServiceRoute(typeof(ReqType), "/foo/{Name}", "GET")]
// [MyServiceRoute("/foo/{Name}", "GET")]
// [MyServiceRoute(typeof(ReqType), "/foo/{Name}")]
if (route.RequestType == null)
{
route.RequestType = routedMethod.GetParameterListFromCache().First().ParameterType;
}
if (string.IsNullOrWhiteSpace(route.Verbs))
{
var upperRoutedMethodName = routedMethod.Name.ToUpperInvariant();
route.Verbs = upperRoutedMethodName != "ANY" ? upperRoutedMethodName : null;
}
routes.Add(route.RequestType, route.RestPath, route.Verbs);
}
}
return routes;
}
I call this method in AppHost.Configure, along with AddFromAssembly:
this.SetConfig(new EndpointHostConfig { ServiceStackHandlerFactoryPath = "service" });
// some container registrations here
this.Routes.AddFromServiceAttributes().AddFromAssembly();
What puzzles me is that the metadata page shows routes correctly.
DTOs are very simple, and they do include the Name string property.
class Foo { public string Name { get; set; } }
Edit 2:
I removed the MyServiceRouteAttribute attribute and reused ServiceStack's RouteAttribute.
Request DTO Types are inferred from 1st method param.
Edit 3:
Probably I managed to solve this. I was preprending /json/reply in the url.
http://localhost/service/json/reply/foo/NameValueHere <- not working
http://localhost/service/foo/NameValueHere <- working
I thought both the content-type and reply-type tokens were mandatory.

Resources