I have a code which changes the UserId as it is written all over.
I've implemented the IUserIdProvider:
public class CustomUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
string userId = "userName";
return userId;
}
}
And added this provider to the startup class (Configuration function):
var idProvider = new CustomUserIdProvider();
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => idProvider);
I expect that all User Ids in all the hub will be "userName".
I know (or maybe not?) that in order to obtain the UserId in one of the Hub functions, I need to get the value of:
Context.User.Identity.Name
But in fact, this value doesn't contain "userName", but different values:
Empty when it's anonymous authentication
The Windows user name in case of Windows Authentication.
In the Hub, when I look at Clients.User("userName"), I see that it's the real connection Id, so something happened (and the debugger stops at the implemented GetUserId function), but still, In the production code I won't know anything about the Custom User Id, and I want to fetch it somehow...
More Info: VS2012, IIS Express.
Related
Background
I've created a working bot in C# but I'm failing to expand it to be a multi-tenant bot. I have created multiple bots in the Microsoft portal using this technique to identify themselves from the messaging endpoint:
https://example.com/api/messages/bot1
https://example.com/api/messages/bot2
https://example.com/api/messages/bot3
I can grab the LastSegment from the URL while in the MessagesController and store it in PrivateConversationData so I know which bot is talking in the current conversation. I intended use this stored 'bot id' in order to retrieve the Microsoft AppId & Password from the web.config (the bot's credentials are stored as a series of custom entries and not the standard appSettings as that only works for a single bot).
Credentials Problem
The authentication works well (nearly) as described here except when using async code with .ConfigureAwait(false) I can't get the HttpContext.Current as it becomes null when running on a different thread. This means I can't get the authenticated user's credentials either by looking them up in the web.config or by calling GetCredentialsFromClaims() since I've lost the authenticated user. If I use .ConfigureAwait(true) I just get deadlocks all over the place.
I have the credentials in the web.config but they are stored per bot and I need the 'bot id' from the URL above in order to get the credentials.
Question
The crux of the problem is: I need the URL to get the 'bot id' and I need the 'bot id' to get the credentials from the web.config but I can never reliably get access to the URL once I've passed a .ConfigureAwait(false) in the code. On the flip side, I can't get the 'bot id' from the PrivateConversationData since I need the bot's credentials in order to load it. A bit chicken and egg :-(
If anyone has any ideas of what I may be doing wrong or has an alternative approach to know which 'bot id' is currently executing I'd very much appreciate it.
Thanks
Please find below given the sample code.
public class StartUp {
public void Configuration(IAppBuilder app) {
var builder = new ContainerBuilder();
//Note: Initialize / register the Metadata Service that can bring the tenant details from the corresponding store
builder.RegisterType<TenantMetadataService>().As<ITenantMetadataService>();
//Note: This helps you in accessing the TenantMetadata from any constructor going forward after the below registry
builder.Register(ti => TenantMetadata.GetTenantMetadataFromRequest()).InstancePerRequest();
//TODO: Register the various services / controllers etc which may require the tenant details here
}
}
public class TenantMetadata {
public Guid TenantId { get;set; }
public Uri TenantUrl { get;set; }
public string TenantName { get;set; }
public static TenantMetadata GetTenantMetadataFromRequest() {
var context = HttpContext.Current;
//TODO: If you have any header like TenantId coming from the request, you can read and use it
var tenantIdFromRequestHeader = "";
//TODO: There will be a lazy cache that keeps building the data as new tenant's login or use the application
if(TenantCache.Contains(...))return TenantCache[Key];
//TODO: Do a look-up from the above step and then construct the metadata
var tenantMetadata = metadataSvc.GetTenantMetadata(...);
//TODO: If the data match does not happen from the Step2, build the cache and then return the value.
TenantCache.Add(key,tenantMetadata);
return tenantMetadata;
}
}
Note
The above code snippet uses the various service placeholders, cache and the other methods which will require to be used based on the designed application services. If you wish not to cache the tenant metadata, if it may contain some sensitive data, you can remove the caching implementation parts.
This implementation can be spread across all your web facing portals like your Web UI, Web Api and WebJobs etc so that it is same across all apps and it is easy to test and consume.
HTH.
I am working with the xamarin.forms app generated when you download the sample for working with Azure Mobile Services. I have made some modifications. Firstly, I have changed Todo, to entry.cs:
public class entry
{
string id;
[JsonProperty("ID")]
public string ID { get; set; }
[JsonProperty("Time")]
public int Time { get; set; }
[JsonProperty("Percentage")]
public int Percentage { get; set; }
//I have omitted Device, Replacement, Use_profile, Longitude, Latitude, Battery
}
I try to add a new line to the table in my SQL database, by calling the following code from my page in cs:
var data = new entry{ Longitude = await GetLongitude(), Latitude = await GetLatitude(), Percentage = bpm }; // initialise new data entry
await AddItem (data);
When this is called, the app crashes.
Here is a gist of the log when the exception is thrown. It gives a Microsoft.WindowsAzure.MobileServices.MobileServiceInvalidOpperationException has been thrown
Explanation:
The resource you are looking for has been removed, had it's name changed, or is temporarily unavailable
This is thrown on the UIApplication.Main (args, null, "AppDelegate"); line in main.cs under the ios project.
Any thoughts on why this is happening would be much appreciated.
UPDATE:
Just to add a little more info, I have a web service setup at http://project.azurewebsites.net this is the address referenced in the constants section of the mobile application I am building in xamarin. However, the SQL database is at http://project-db.database.windows.net how do I get around this? Can I create a database on the original domain, or change the reference in the app?
It looks like you are getting a 404 error when you are calling your Mobile Backend. You need to add a new Table Controller to handle the "entry" class, because your client will be trying to post to https://yourservice.azurewebsites.net/tables/entry, which doesn't exist.
In your server project, you need to add a new class Entry that inherits from EntityData. Then you add this type to your DbContext class and add a table controller. This tutorial for Mobile Services controllers might be helpful. If you're using Mobile Apps, you would use Add -> New Scaffolded Item -> Azure Mobile Apps -> Mobile Apps Table Controller.
Then, deploy your server project so that the new REST endpoint is available and then your client app should be able to connect.
Edited to add: you specify the connect to the SQL Database in the MS_TableConnectionString setting in web.config. Whatever value is the Connection Strings section of the Azure Portal will override this. For more information, see https://azure.microsoft.com/en-us/documentation/articles/web-sites-configure/.
I'm building a multi-tenant MVC app where there's a single app pool and single database. I have a Tenant table, and each of my models has a TenantId identified.
Each Tenant has a string "Url" that identifies the full URL used to access that tenant's data.
I can access this from my BaseController with the following (rough approximation):
HttpRequest request = HttpContext.Current.Request;
Uri requestUrl = request.Url;
_tenant = _tenantService.GetTenantByUrl(requestUrl);
Now, I'm at a point where I need to pass the Tenant into the service layer to perform business logic. One way I can do this is to go across every single method across all services (~200 methods) and add a Tenant parameter. I'd have to touch every call to the service layer, and every service layer method. This would work, but it's tedious and muddles the code.
For example, one of my methods before:
public void DeleteUserById(int userId)
{
using (var db = CreateContext())
{
var user = db.Users.FirstOrDefault(u => u.UserId.Equals(userId));
InternalDeleteUser(db, user);
}
}
After (if I pass in the Tenant):
public void DeleteUserById(Tenant tenant, int userId)
{
using (var db = CreateContext())
{
var user = tenant.Users.FirstOrDefault(u => u.UserId.Equals(userId));
InternalDeleteUser(db, user);
}
}
What I'm trying to achieve (by setting the tenant from my BaseController, one layer up):
public void DeleteUserById(int userId)
{
using (var db = CreateContext())
{
var user = _tenant.Users.FirstOrDefault(u => u.UserId.Equals(userId));
InternalDeleteUser(db, user);
}
}
Is there any way I can use my BaseService (all other services inherit from this) or some other pattern to define the Tenant from the Controller, and have the service methods pick it up, without passing it as a parameter to each one? This way I need only touch the base controller (or maybe even global.asax), and nothing else.
Put simply: How can I make an object accessible to all services by defining it from an MVC controller, without passing it directly to the service?
I guess what you´re saying about having a base service (see Layer Supertype) makes sense. That base class will have a dependency on an interface defined in the same service layer (e.g. IUserSession, IContext or whatever) and that interface will have a method or property that will return your Tenant.
The implementation of this interface will reside in your web application and it will do something as what you described, obtaining the data from the HttpContext.
If you have a background process, console application or whatever that does not run on a web context, you will have a different implementation that will create the Tenant based on any other criteria that you want.
So to summarize, you will have in your service layer:
abstract class BaseService
{
protected IContext Context {get; private set;}
public BaseService(IContext context)
{
Context = context;
}
}
public interface IContext
{
Tenant GetTenant();
}
Then in your web layer you´ll have:
public IWebContext : IContext
{
public Tenant GetTenant()
{
//your code to return create the tenant based on the url.
}
}
Hope this helps.
I have the same 'problem' since I'm building a multi tenant app as well. However, I solved it quite simple, IMO: every repository/service has defined a TenantId property, that must be set when that service is used. TenantId is a value object and it will throw if null.
Now, the point is any of the repos/services can be used outside the request, for example in a background thread or app. I am using a message driven approach so any required info (like tenant id) is part of the message and thus available for the consumer of the service (the message handler). Another benefit is testability.
I advice against coupling your service to a request specific object like HttpContext, Session or Cache.
I am moving all of the Authentication and Security concerns into my Data Access Layer on an ASP.NET MVC4 Internet Application. Everything is fine for logging in, logging out, creating users etc but I am hitting a stumbling block with adding users to roles.
After Creating a user account I want to add them to some default roles. The method for doing so looks like this
public static string CreateUserAccount(string username, string password)
{
WebSecurity.CreateUserAndAccount(username, password);
var roleProvider = new SimpleRoleProvider();
roleProvider.AddUsersToRoles(new[] {username}, new[] {"MeterInfo", "SiteInfo", "AMRInfo", "InstallImages"});
return username + " Account Created";
}
The call to WebSecurity for creating the account is OK, but my use of SimpleRoleProvider causes this error
You must call the "WebSecurity.InitializeDatabaseConnection" method before you call any other method of the "WebSecurity" class. This call should be placed in an _AppStart.cshtml file in the root of your site.
The InitializeDatabaseConnection is already handled in the AuthConfig which is called on startup by global.asax.
AssetRegisterDataLayer.DataAccess.Security.InitializeSecurity();
The method being called on my DataAccess layer looks like this
public static void InitializeSecurity()
{
WebSecurity.InitializeDatabaseConnection("AssetRegisterDb","UserProfile","UserId","UserName", false);
}
I have seen this issue happen when people use the out of the box config for MVC4 where the Accounts controller is decorated with the [InitializeSimpleMembership] attribute instead of calling the WebSecurity initializer at application start, but that is not the case here. Anyone know why all the WebSecurity works except roles?
Thanks very much
I have found my mistake, I will answer my own question in case someone else has a similar issue.
The error in the code shown in my question was instantiating a new SimpleRoleProvider. I should have done this
public static string CreateUserAccount(string username, string password)
{
WebSecurity.CreateUserAndAccount(username, password);
var roleProvider = (SimpleRoleProvider)Roles.Provider;
roleProvider.AddUsersToRoles(new[] {username}, new[] {"MeterInfo", "SiteInfo", "AMRInfo", "InstallImages"});
return username + " Account Created";
}
Once authenticatated I use HttpContext.Current.User.Identity.Name; to ensure user is authorized to view a part of my site.
When I access certain parts of my site I need to get the User and get which context (organization they are logged into), url would be something like settings/supercompany/profile. where supercompany is the current context.
For each user I would need to check if they are admin in that company or a general user, if a general user then they cannot see certain things.
public class SettingsApi
{
private readonly string _userId;
private readonly string _contextId;
public SettingsApi(string userId, string contextId)
{
_userId = userId;
_contextId = contextId;
}
}
If I instantiate the class above from a controller (post or get), would caching somehow mess things up? Users role changed and I don't pick it up? Would something like the below work well?
var settings = new SettingsApi(HttpContext.Current.User.Identity.Name, currentContextId);
settings.IsAdmin();
Note: I would have used attributes to authorize but my requirements are I need to pick out the currentContext from the URL plus I need to use the class above elsewhere in my code.
Update
AuthorizeAttribute works well with caching, but the method used to authorize i.e.
protected override bool AuthorizeCore(HttpContextBase httpContext)
Will not hand me back an instance of the class I need...
Update 2 I don't want this class or an instance of this class to be cached in anyway, everytime I ask for a new instance I don't mind fetching one from the DB...
My Question - is the way I am coding ok? Will my user and his permissions NOT be cached?
It is possible, if you're not careful, to let MVC cache the output of the first request by an authenticated user. I use VaryByCustom and the current identity's name.
[OutputCache(VaryByCustom="user")]
public class SomeController : Controller
{
// etc.
}
In my Global.asax.cs I define:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom.Equals("user", StringComparison.OrdinalIgnoreCase))
{
return context.User.Identity.IsAuthenticated ? context.User.Identity.Name : string.Empty;
}
return base.GetVaryByCustomString(context, custom);
}
If you are proposing to add instances of the SettingsApi to the cache then it definitely will not work as caching is app wide and so all users will end up sharing the same SettingsApi. Using the OutputCache should be fine (as long as you dont do something like put userid in a hidden field and use [OutputCache(VaryByCustom="user")] or similar).
If you are looking to cache the SettingsApi you should do so through SessionState which is per user/session and wont affect the authentication.