I was having trouble manipulating my database, after several analyzes I realized that the code below was causing such an error.
var serviceProvider = services.BuildServiceProvider();
var roleManager = serviceProvider.GetService<RoleManager<IdentityRole>>();
var roleExist = roleManager.RoleExistsAsync("Administrador").Result;
if (!roleExist)
{
var role = new IdentityRole();
role.Name = "Administrador";
var result = roleManager.CreateAsync(role).Result;
if (!result.Succeeded)
{
throw new Exception($"Erro ao criar a Role 'Administrador': {result.Errors}");
}
}
By commenting such code I was able to manipulate my database (update, delete...). I would like to know the reason for such a problem, and also how can I use such code without this problem.
Related
I am trying to integrate the power bi embedded with C#, I always have this same error that comes out, I put it to you just below, as well as the versions of the packages and the code (basic) which is supposed to do the work .
Thank you for all your answers
Microsoft.PowerBI.Api (v2.0.12)
Microsoft.PowerBI.JavaScript (v2.5.1)
Microsoft.IdentityModel.Clients.ActiveDirectory (v3.13.9)
Microsoft PowerBI JavaScript (v2.5.1)
Microsoft IdentityModel Clients.ActiveDirectory (v3.13.9)
Note that the two head variables are temporary.
The error always come out at this line : var authenticationResult = await authenticationContext.AcquireTokenAsync(this.resourceUrl, this.applicationId, credential);
There is the error message : "exceptionMessage": "AADSTS500011: The resource principal named https://analysis.windows.net/powerbi/api/ was not found in the tenant named x. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.
public async Task<EmbedConfigResource> EmbedReport([FromUri]string username, [FromUri]string roles)
{
roles = "None";
username = this.pbiUsername;
var result = new EmbedConfigResource { Username = username, Roles = roles };
var credential = new UserPasswordCredential(this.pbiUsername, this.pbiPassword);
var authenticationContext = new AuthenticationContext(this.authorityUrl);
var authenticationResult = await authenticationContext.AcquireTokenAsync(this.resourceUrl, this.applicationId, credential);
var tokenCredentials = new TokenCredentials(authenticationResult.AccessToken, "Bearer");
using (var client = new PowerBIClient(new Uri(this.apiUrl), tokenCredentials))
{
var reports = await client.Reports.GetReportsInGroupAsync(this.workspaceId);
Report report = reports.Value.FirstOrDefault(r => r.Id == this.reportId);
var datasets = await client.Datasets.GetDatasetByIdInGroupAsync(this.workspaceId, report.DatasetId);
result.IsEffectiveIdentityRequired = datasets.IsEffectiveIdentityRequired;
result.IsEffectiveIdentityRolesRequired = datasets.IsEffectiveIdentityRolesRequired;
GenerateTokenRequest generateTokenRequestParameters;
var rls = new EffectiveIdentity(this.pbiUsername, new List<string> { report.DatasetId });
if (!string.IsNullOrWhiteSpace(roles))
{
var rolesList = new List<string>();
rolesList.AddRange(roles.Split(','));
rls.Roles = rolesList;
}
generateTokenRequestParameters = new GenerateTokenRequest(accessLevel: "view", identities: new List<EffectiveIdentity> { rls });
var tokenResponse = await client.Reports.GenerateTokenInGroupAsync(this.workspaceId, report.Id, generateTokenRequestParameters);
result.EmbedToken = tokenResponse;
result.EmbedUrl = report.EmbedUrl;
result.Id = report.Id;
return result;
}
}
You must log into Azure portal, go to Azure Active Directory -> App registrations, select your app, click View API permissions, and then grant admin consent by clicking the button at the bottom:
If you don't have access to the portal, or the button is disabled, you must ask your admin to do it for you.
I created a website and published it to Azure. Now I want to protect some pages with authentication so I added the following code in the method Seed of Migrations.Configuration and published it to Azure.
However, the code is run even on local. The table AspNetRoles is still empty on both local and Azure SQL server. I tried to use Update-Database -Script -SourceMigration:0 to generate all the SQL statements but there is no SQL inserting the initial data to these tables of Asp.Net identity.
// Create role
var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
const string roleName = "CanEdit";
var role = roleManager.FindByName(roleName);
if (role == null)
{
role = new Microsoft.AspNet.Identity.EntityFramework.IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}
var memberEmails = Properties.Settings.Default.CanEditMembers.Split(';');
foreach (var email in memberEmails)
{
var user = userManager.FindByName(email.Trim());
if (user != null)
{
var rolesForUser = userManager.GetRoles(user.Id);
if (!rolesForUser.Contains(role.Name))
{
var result = userManager.AddToRole(user.Id, role.Name);
}
}
}
Basically I want to insert 'CanEdit' to the AspNetRoles (I guess) and insert some other values (Users and their associations with the role) to these AspNetUser.... tables. Some of my Controls are already decorated with attribute [Authorize(Roles = "CanEdit")].
I am having trouble figuring out how to seed additional users and roles into my MVC5 application, using EF6 code first. In order to debug the Seed method from the Configure.cs since update-database was not working, I wrote this controller,
public ActionResult test() {
var context = new ApplicationDbContext();
var roleStore = new RoleStore<IdentityRole>(context);
var roleManager = new RoleManager<IdentityRole>(roleStore);
roleManager.Create(new IdentityRole { Name = "basic" });
var userStore = new UserStore<ApplicationUser>(context);
var userManager = new UserManager<ApplicationUser>(userStore);
var adminthere = context.Users.Any(n => n.UserName == "Admin");
var basicthere = context.Users.Any(n => n.UserName == "Basic");
// Create Dummy basic account
if (!basicthere) {
var basicUser = new ApplicationUser { UserName = "Basic" };
userManager.Create(basicUser, "test");
var _id = basicUser.Id;
userManager.AddToRole(basicUser.Id, "basic");
}
return View();
}
The debugger throws an exception at the userManager.AddToRole(basicUser.Id, "basic"); call saying "UserID not found"? Here is a screenshot including variable values from the debug session:
What is the problem? Also, the exact same code (changing the words "basic" for "Admin") works for seeding the database with the Admin user in role "admin". Why?
EDIT EDIT: moved edit I posted here previoulsy to a real answer below.
As the comments suggested I will post my this as an answer:
The line of code userManager.Create(basicUser, "test"); didn't succeed - the passwort must at least have 6 characters. So while creating the basicUser ApplicationUser instance worked (and hence the _id was not null) I didn't have an IdentityUser of that _id. On admin it succeeded previously bc. I had a different pwd that I didn't want to post here ...
Sometimes users get Invalid Token when clicking on their email confirmation link. I can't figure out why, it's purely random.
Here is the code that creates the user:
IdentityResult result = manager.Create(user, "Password134567");
if (result.Succeeded)
{
var provider = new DpapiDataProtectionProvider("WebApp2015");
UserManager<User> userManager = new UserManager<User>(new UserStore<User>());
userManager.UserTokenProvider = new DataProtectorTokenProvider<User>(provider.Create(user.Id));
manager.UserTokenProvider = new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));
var emailInfo = new Email();
string code = HttpUtility.UrlEncode(Context.GetOwinContext().GetUserManager<ApplicationUserManager>().GenerateEmailConfirmationToken(user.Id));
string callbackUrl = IdentityHelper.GetUserConfirmationRedirectUrl(code, user.Id, Request);
if (email.IndexOf("#") != -1)
{
if (assignedId == 0)
{
lblError.Text = "There was an error adding this user";
return;
}
string emailcontent = emailInfo.GetActivationEmailContent(assignedId, callbackUrl, userRole);
string subject = emailInfo.Subject;
if (string.IsNullOrEmpty(subject))
{
subject = "Your Membership";
}
Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.SendEmail(user.Id, subject, emailcontent);
if (user.EmailConfirmed)
{
IdentityModels.IdentityHelper.SignIn(manager, user, isPersistent: false);
IdentityHelper.RedirectToReturnUrl(Request.QueryString["ReturnUrl"], Response);
}
else
{
ErrorMessage.ForeColor = Color.Green;
ErrorMessage.Text = "An email has been sent to the user, once they verify their email they are ready to login.";
}
}
else
{
ErrorMessage.ForeColor = System.Drawing.Color.Green;
ErrorMessage.Text = "User has been created.";
}
var ra = new RoleActions();
ra.AddUserToRoll(txtEmail.Text, txtEmail.Text, userRole);
}
else
{
ErrorMessage.Text = result.Errors.FirstOrDefault();
}
Here is the confirmation page that gives the 'invalid token' error
protected void Page_Load(object sender, EventArgs e)
{
var code = IdentityHelper.GetCodeFromRequest(Request);
var userId = IdentityHelper.GetUserIdFromRequest(Request);
if (code != null && userId != null)
{
var manager = Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
var confirmId = manager.FindById(userId);
if (confirmId != null)
{
var result = manager.ConfirmEmail(userId, HttpUtility.UrlDecode(code));
if (result.Succeeded)
{
return;
}
else
{
lblError.Text = result.Errors.FirstOrDefault();
txtNewPassword.TextMode= TextBoxMode.SingleLine;
txtNewPassword.Text = "Error contact support";
txtNewPassword2.TextMode= TextBoxMode.SingleLine;
txtNewPassword2.Text = result.Errors.FirstOrDefault();
txtNewPassword.Enabled = false;
txtNewPassword2.Enabled = false;
imageButton1.Enabled = false;
}
}
else
{
lblError.Text = "Account Does Not Exist";
imageButton1.Enabled = false;
}
}
}
Live Demo Project
I've created a pared-down demo project for you. It's hosted on GitHub here and is live on Azure here. It works as designed (see edits about Azure Websites) and uses a similar but not identical approach as you used.
It started with this tutorial, and then I removed the cruft that came with this NuGet demo code:
Install-Package -Prerelease Microsoft.AspNet.Identity.Samples
For your purposes, my demo code is more relevant than the NuGet sample is, because it focuses just on token creation and validation. In particular, take a look at these two files:
Startup.Auth.cs.
We're instantiating the IDataProtectionProvider only once per application start.
public partial class Startup
{
public static IDataProtectionProvider DataProtectionProvider
{
get;
private set;
}
public void ConfigureAuth(IAppBuilder app)
{
DataProtectionProvider =
new DpapiDataProtectionProvider("WebApp2015");
// other code removed
}
}
AccountController.cs.
Then within the AccountController, we're using the static provider instead of creating a new one.
userManager.UserTokenProvider =
new DataProtectorTokenProvider<User>(
Startup.DataProtectionProvider.Create("UserToken"));
Just doing that might remove the bug you're seeing. Here are some questions for you to consider while troubleshooting further.
Are you using two different UserTokenProvider purposes?
The DataProtectorTokenProvider.Create(string[] purposes) method takes a purposes argument. Here is what MSDN has to say about that:
purposes. Additional entropy used to ensure protected data may only be unprotected for the correct purposes.
When you create the user code, you're using (at least) two different purposes:
user.Id
"ConfirmUser" and
the purpose of the ApplicationUserManager that you retrieve with GetOwinContext()....
Here's your code as a snippet.
userManager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create(user.Id));
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));
string code = Context
.GetOwinContext()
.GetUserManager<ApplicationUserManager ()
.GenerateEmailConfirmationToken(user.Id)
When you validate the code, you might be using the wrong purpose. Where do you assign the UserTokenProvider for the ApplicationUserManager that you use to confirm the email? It's purposes argument must be the same!
var manager = Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
var result = manager.ConfirmEmail(userId, HttpUtility.UrlDecode(code));
There is a strong chance that the token is invalid because you're sometimes using a different UserTokenProvider purpose for creation than you're using for validation.
Why would this be sometimes? Do a thorough search of your code to find all the places that assign to UserTokenProvider. Maybe you override it somewhere unexpected (such as in a property or in the IdentityConfig.cs file) so that it seems random.
Has the TokenLifespan expired?
You've mentioned that the Invalid Token message occurs randomly. It might be that the token has expired. This tutorial notes that the default lifespan is one day. You can change it like this:
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser>
(dataProtectionProvider.Create("WebApp2015"))
{
TokenLifespan = TimeSpan.FromHours(3000)
};
Why three UserManager instances?
Here are some comments on the code that creates the confirmation token. It seems that you're using a three separate UserManager instances including a derived ApplicationUserManager type. What's that about?
What is the type of manager here?
Why create a userManager instead of using the existing manager?
Why use manager.UserTokenProvider not userManager.UserTokenProvider?
Why are you getting a third UserManager instance from the Context?
Note that I have removed a lot of code to focus on just your token creation.
// 1.
IdentityResult result = manager.Create(user, "Password134567");
if (result.Succeeded)
{
var provider = new DpapiDataProtectionProvider("WebApp2015");
// 2.
UserManager<User> userManager =
new UserManager<User>(new UserStore<User>());
userManager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create(user.Id));
// 3.
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create("ConfirmUser"));
// 4.
string raw = Context.GetOwinContext()
.GetUserManager<ApplicationUserManager>()
.GenerateEmailConfirmationToken(user.Id)
// remaining code removed
}
I wonder whether we could simplify the above to use just one UserManager instance as follows.
// 1.
IdentityResult result = manager.Create(user, "Password134567");
if (result.Succeeded)
{
var provider = new DpapiDataProtectionProvider("WebApp2015");
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider.Create(user.Id));
// 3.
var provider = provider.Create("ConfirmUser");
manager.UserTokenProvider =
new DataProtectorTokenProvider<User>(provider);
// 4.
string raw = manager.GenerateEmailConfirmationToken(user.Id);
// remaining code removed
}
If you use this approach, be sure to use the same "ConfirmUser" purposes argument during confirmation of the email.
What's inside IdentityHelper?
Since the error is happening randomly, it occurs to me that the IdentityHelper methods might be doing something funky to the code that mucks up things. What's inside each of these methods?
IdentityHelper.GetUserConfirmationRedirectUrl()
IdentityHelper.RedirectToReturnUrl()
IdentityHelper.GetCodeFromRequest()
IdentityHelper.GetUserIdFromRequest()
I might write some tests to ensure that the raw code that your process creates always matches the raw code that your process retrieves from the Request. In pseudo-code:
var code01 = CreateCode();
var code02 = UrlEncode(code01);
var request = CreateTheRequest(code02);
var response = GetTheResponse();
var code03 = GetTheCode(response);
var code04 = UrlDecode(code03);
Assert.AreEquals(code01, code04);
Run the above 10,000 times to ensure that no problems exist.
Conclusion
It's my strong suspicion that the problem lies in using one purposes argument during token creation and another during confirmation. Use one purpose only and you might be fine.
Making this work on Azure Websites
Use SqlCompact instead of localdb.
Use app.GetDataProtectionProvider() not DpapiDataProtectionProvider because Dpapi does not work with web farms.
Is the site hosted on multiple web servers?
If so, you cannot use DPAPI here. It is machine-specific.
You'll need to use another data protection provider.
There might be some url-invalid characters in the code (token). Thus we need to use HttpUtility.UrlEncode(token) and HttpUtility.UrlDecode(token) when it appears in any url.
See something details here:
Identity password reset token is invalid
Whilst I think Shaun has provided great feedback to resolve such issues. There is one comment you made that makes me think it just might be an Alternate token issue. See also comments about multiple servers and Tokens.
... it's purely random
I dont think its random ;-). But what could make it work much of the time then for some users not.
A token was generated for a different page or APP, or is from the same APP and has just expired. Eg token valid for a short period. Is present in the browser. The token is from a similar app or from the same app on a different form/page.
The browser present the token since the token was generated for the Domain.
But when analyzed the token didnt match, or just expired.
Consider the lifecycle of the tokens.
I created a new ASP.NET MVC-5 application with Individual User Accounts and then updated all the Nuget packages in the solution. Now I'm trying to follow some of the guidelines shown in some tutorials but I encountered some problems.
The first one is that a class called ApplicationRoleManager which is being used throughout the application wasn't created (the ApplicationUserManager was created).
The second problem is more about Entity-Framework: I've seen that for seeding the database with a user and role many people create a static constructor in the ApplicationDbContext class:
static ApplicationDbContext()
{
Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
}
So I added it, and the implementation of the ApplicationDbInitializer is:
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext>
{
protected override void Seed(ApplicationDbContext context)
{
InitializeIdentityForEF(context);
base.Seed(context);
}
//Create User=Admin#Admin.com with password=Admin#123456 in the Admin role
public static void InitializeIdentityForEF(ApplicationDbContext db)
{
var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
const string name = "admin#admin.com";
const string password = "Admin#123456";
const string roleName = "Admin";
//Create Role Admin if it does not exist
var role = roleManager.FindByName(roleName);
if (role == null)
{
role = new IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}
var user = userManager.FindByName(name);
if (user == null)
{
user = new ApplicationUser { UserName = name, Email = name };
var result = userManager.Create(user, password);
result = userManager.SetLockoutEnabled(user.Id, false);
}
// Add user admin to Role Admin if not already added
var rolesForUser = userManager.GetRoles(user.Id);
if (!rolesForUser.Contains(role.Name))
{
var result = userManager.AddToRole(user.Id, role.Name);
}
}
After adding everything I opened the Package Manager Console and typed Enable-Migrations, then Add-Migration someName and then Update-Database.
the results were that the database was created successfully but no data was inserted to the database.
After noticing the data wasn't inserted I moved the Seed logic to the Index method of the home controller and the data was inserted after running the application.
I also needed to add this line: app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
to the Startup.Auth.cs file.
So my questions are:
Do I really need to enter the ApplicationRoleManager class
manually?
How do I make the seed method work?
UPDATE
I've changed the Seed method to:
protected override void Seed(ApplicationDbContext context)
{
var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
//since there is no ApplicationRoleManager (why is that?) this is how i create it
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
const string name = "admin#admin.com";
const string password = "Admin#123456";
const string roleName = "Admin";
//Create Role Admin if it does not exist
var role = roleManager.FindByName(roleName);
if (role == null)
{
role = new IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}
//app hangs here...
var user = userManager.FindByName(name);
if (user == null)
{
user = new ApplicationUser { UserName = name, Email = name };
var result = userManager.Create(user, password);
result = userManager.SetLockoutEnabled(user.Id, false);
}
// Add user admin to Role Admin if not already added
var rolesForUser = userManager.GetRoles(user.Id);
if (!rolesForUser.Contains(role.Name))
{
var result = userManager.AddToRole(user.Id, role.Name);
}
base.Seed(context);
}
So now, the Admin role is created but when getting to var user = userManager.FindByName(name); the application hangs with no exception or any message...
When using migrations you can use the built in initializer and the Seed method:
Database.SetInitializer<ApplicationDbContext>(new
MigrateDatabaseToLatestVersion<ApplicationDbContext,
APPLICATION.Migrations.Configuration>());
and in APPLICATION.Migrations.Configuration (this was created by the Enable-Migrations command):
protected override void Seed(ApplicationDbContext context)
{
// seed logic
}
As a role manager you can also use the RoleManager<ApplicationRole> base implementation.
I also was a bit confused about hanging of application in this case. The problem can be solved in this way
var userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUserManager>(db));
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(db));
And for anyone using the ApplicationUser with Integer foreign key, the code is this one:
var userManager = new ApplicationUserManager(new ApplicationUserStore(context));
var roleManager = new ApplicationRoleManager(new ApplicationRoleStore(context));
This works great for default MVC 5 project.
var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context));
It doesn't appear that the solutions posted address the issue of the app hanging on the call of userManager.FindByName(name). I'm running into the same problem. It worked a few hours ago on my local. I published to Azure and it started hanging. When I tested my local again it all of a sudden started hanging at that step. No error is returned and no timeout (at least after waiting 10-15 minutes). Does anyone have any tips to address Yoav's ultimate question?
I have some other very simple seeding processes that run before adding roles, and db.Foo.AddOrUpdate(foo) calls are running without error, but not actually saving anything to the database.
I just spent a deeply unpleasant half day dealing with this. I finally managed to get the damn thing to fire:
public static void InitializeIdentityForEF(ApplicationDbContext context)
{
context.Configuration.LazyLoadingEnabled = true;
//var userManager = HttpContext.Current
// .GetOwinContext().GetUserManager<ApplicationUserManager>();
//var roleManager = HttpContext.Current
// .GetOwinContext().Get<ApplicationRoleManager>();
var roleStore = new RoleStore<ApplicationRole, int, ApplicationUserRole>(context);
var roleManager = new RoleManager<ApplicationRole, int>(roleStore);
var userStore = new UserStore<ApplicationUser, ApplicationRole, int, ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>(context);
var userManager = new UserManager<ApplicationUser, int>(userStore);
...
It's the end of an extremely long day, and I suspect someone's going to tell me why I shouldn't do this. The rest of my Seed method fires beautifully, however, using non-async methods (FindByName/Create).
Sir goobering,
You struggles have helped me get passed this problem, I had to do it a little different though.
context.Configuration.LazyLoadingEnabled = true;
//var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
//var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
const string name = "admin#example.com";
const string password = "Admin#123456";
const string roleName = "Admin";
***var userManager = new ApplicationUserManager(new UserStore<ApplicationUser>(context));
var roleManager = new ApplicationRoleManager(new RoleStore<IdentityRole>(context));***
//Create Role Admin if it does not exist
var role = roleManager.FindByName(roleName);
if (role == null) {
role = new IdentityRole(roleName);
var roleresult = roleManager.Create(role);
}