User authentication, roles and permissions - servicestack

In the AppHost module, I'm opening/creating an NHibernate based authentication repository (using the "ServiceStack.Authentication.NHibernate" module), and subsequently creating a default user:
HibernateFactory = RepoDatabaseFactory(typeof(ServiceStack.Authentication.NHibernate.UserAuthMap).Assembly);
NHSession = HibernateFactory.OpenSession();
container.Register(NHSession);
NHibernateUserAuthRepository UserRepository = new NHibernateUserAuthRepository(HibernateFactory);
container.Register(UserRepository);
CurrentSessionContext.Bind(NHSession);
var Authorization = UserRepository.GetUserAuthByUserName("miga");
if (Authorization == null)
{
UserRepository.CreateUserAuth(
new UserAuth
{
UserName = "miga",
FirstName = "xxxxxx",
LastName = "xxxxxx",
Address = "xxxxxxxxxxxx",
PostalCode = "xxxxxx",
City = "xxxxxx",
Country = "xxxxx",
Gender = "xxxxx",
PhoneNumber = "xxxxxx",
Email = "xxxxxxx",
Roles = new List<string> { RoleNames.Admin },
Culture = "xxxxx"
},
"xxxxxx");
}
container.Register<ICacheClient>(new MemoryCacheClient());
UserRepository.InitSchema();
where the RepoDatabaseFactory is:
public NHibernate.ISessionFactory RepoDatabaseFactory(Assembly AuthAssembly)
{
var Configuration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(ConnString).UseReflectionOptimizer()).Mappings(m =>
{
m.FluentMappings.AddFromAssembly(AuthAssembly);
})
.CurrentSessionContext("web")
.BuildConfiguration();
var Exporter = new SchemaExport(Configuration);
Exporter.Execute(false, false, false);
var SessionFactory = Configuration.BuildSessionFactory();
return (SessionFactory);
}
To a certain extent this works; i.e. the relevant tables are created ("UserAuth", "UserAuth_Permissions", "UserAuth_Roles", "UserOAuthProvider" and "UserOAuthProvider_Items"). When creating the user "miga" as above, you'll notice the line "Roles = new List { RoleNames.Admin }" in the "new UserAuth" statement. Subsequently, the user data above is in fact added to the "UserAuth" table, but the "Admin" role is not added to the "UserAuth_Roles" table as I would have expected. Inserting a new record in the "UserAuth_Roles" table manually (containing the user id of the "miga" user and the admin rolename - e.g. 1, "Admin") still - it appears - does not provide the "miga" user with an "Admin" role:
Using the following fragment:
var Client = new RestClient("http://localhost:42147/");
Client.Authenticator = new HttpBasicAuthenticator("miga", "xxxxxxxx");
var Request = new RestRequest("assignroles", Method.POST);
Request.RequestFormat = DataFormat.Json;
Request.AddBody(new { UserName = "Sally", Roles = "Write" });
var Response = Client.Post<AssignRoleResponse>(Request);
I get an authorization error with an "Invalid Role" message. So my question is basically how to get this working in an NHibernate context ?

Related

How to get Azure user's client secrete (without registering app) or how to generate bearer access token of current Azure credential?

I am trying to create Resource Group and Virtual Machine (and other components) programmatically using C#. I want to use
SdkContext.AzureCredentialsFactory.FromServicePrincipal(clientId, clientSecrete, tenantId, environment);
But I don't know my client Secrete. How to get it or generate it? Or how to generate the bearer access token of current Azure user credential for restful api call?
The file %userprofile%.azure\accessTokens.json contains bearer access token. But it is generated by Azure Cli. Is there a way to generate the token via C# code?
This article
https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal shows a way but it needs to register an app and grant it certain permission (I don't have the permission to grant the permission to app).
The link you provided is the correct way to use SdkContext.AzureCredentialsFactory.FromServicePrincipal, if you just have a user account rather than a valid service principal, the best way in this case is to use Azure.Identity to auth, make sure your user account has the permission to create the VM, i.e. RBAC role at the scope you want to create the VM.
using Azure.Identity;
using Azure.ResourceManager.Resources;
using Azure.ResourceManager.Resources.Models;
using Azure.ResourceManager.Compute;
using Azure.ResourceManager.Compute.Models;
using Azure.ResourceManager.Network;
using Azure.ResourceManager.Network.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
namespace CreateVMSample
{
public class Program
{
protected static string AdminUsername = "<username>";
protected static string AdminPassword = "<password>";
static async Task Main(string[] args)
{
var subscriptionId = Environment.GetEnvironmentVariable("AZURE_SUBSCRIPTION_ID");
var resourceClient = new ResourcesManagementClient(subscriptionId, new DefaultAzureCredential());
// Create Resource Group
Console.WriteLine("--------Start create group--------");
var resourceGroups = resourceClient.ResourceGroups;
var location = "westus2";
var resourceGroupName = "QuickStartRG";
var resourceGroup = new ResourceGroup(location);
resourceGroup = await resourceGroups.CreateOrUpdateAsync(resourceGroupName, resourceGroup);
Console.WriteLine("--------Finish create group--------");
// Create a Virtual Machine
await Program.CreateVmAsync(subscriptionId, "QuickStartRG", location, "quickstartvm");
// Delete resource group if necessary
//Console.WriteLine("--------Start delete group--------");
//await (await resourceGroups.StartDeleteAsync(resourceGroupName)).WaitForCompletionAsync();
//Console.WriteLine("--------Finish delete group--------");
//Console.ReadKey();
}
public static async Task CreateVmAsync(
string subscriptionId,
string resourceGroupName,
string location,
string vmName)
{
var computeClient = new ComputeManagementClient(subscriptionId, new DefaultAzureCredential());
var networkClient = new NetworkManagementClient(subscriptionId, new DefaultAzureCredential());
var virtualNetworksClient = networkClient.VirtualNetworks;
var networkInterfaceClient = networkClient.NetworkInterfaces;
var publicIpAddressClient = networkClient.PublicIPAddresses;
var availabilitySetsClient = computeClient.AvailabilitySets;
var virtualMachinesClient = computeClient.VirtualMachines;
// Create AvailabilitySet
Console.WriteLine("--------Start create AvailabilitySet--------");
var availabilitySet = new AvailabilitySet(location)
{
PlatformUpdateDomainCount = 5,
PlatformFaultDomainCount = 2,
Sku = new Azure.ResourceManager.Compute.Models.Sku() { Name = "Aligned" }
};
availabilitySet = await availabilitySetsClient.CreateOrUpdateAsync(resourceGroupName, vmName + "_aSet", availabilitySet);
// Create IP Address
Console.WriteLine("--------Start create IP Address--------");
var ipAddress = new PublicIPAddress()
{
PublicIPAddressVersion = Azure.ResourceManager.Network.Models.IPVersion.IPv4,
PublicIPAllocationMethod = IPAllocationMethod.Dynamic,
Location = location,
};
ipAddress = await publicIpAddressClient.StartCreateOrUpdate(resourceGroupName, vmName + "_ip", ipAddress)
.WaitForCompletionAsync();
// Create VNet
Console.WriteLine("--------Start create VNet--------");
var vnet = new VirtualNetwork()
{
Location = location,
AddressSpace = new AddressSpace() { AddressPrefixes = new List<string>() { "10.0.0.0/16" } },
Subnets = new List<Subnet>()
{
new Subnet()
{
Name = "mySubnet",
AddressPrefix = "10.0.0.0/24",
}
},
};
vnet = await virtualNetworksClient
.StartCreateOrUpdate(resourceGroupName, vmName + "_vent", vnet)
.WaitForCompletionAsync();
// Create Network Interface
Console.WriteLine("--------Start create Network Interface--------");
var nic = new NetworkInterface()
{
Location = location,
IpConfigurations = new List<NetworkInterfaceIPConfiguration>()
{
new NetworkInterfaceIPConfiguration()
{
Name = "Primary",
Primary = true,
Subnet = new Subnet() { Id = vnet.Subnets.First().Id },
PrivateIPAllocationMethod = IPAllocationMethod.Dynamic,
PublicIPAddress = new PublicIPAddress() { Id = ipAddress.Id }
}
}
};
nic = await networkInterfaceClient
.StartCreateOrUpdate(resourceGroupName, vmName + "_nic", nic)
.WaitForCompletionAsync();
// Create VM
Console.WriteLine("--------Start create VM--------");
var vm = new VirtualMachine(location)
{
NetworkProfile = new Azure.ResourceManager.Compute.Models.NetworkProfile { NetworkInterfaces = new[] { new NetworkInterfaceReference() { Id = nic.Id } } },
OsProfile = new OSProfile
{
ComputerName = vmName,
AdminUsername = Program.AdminUsername,
AdminPassword = Program.AdminPassword,
LinuxConfiguration = new LinuxConfiguration { DisablePasswordAuthentication = false, ProvisionVMAgent = true }
},
StorageProfile = new StorageProfile()
{
ImageReference = new ImageReference()
{
Offer = "UbuntuServer",
Publisher = "Canonical",
Sku = "18.04-LTS",
Version = "latest"
},
DataDisks = new List<DataDisk>()
},
HardwareProfile = new HardwareProfile() { VmSize = VirtualMachineSizeTypes.StandardB1Ms },
AvailabilitySet = new Azure.ResourceManager.Compute.Models.SubResource() { Id = availabilitySet.Id }
};
var operation = await virtualMachinesClient.StartCreateOrUpdateAsync(resourceGroupName, vmName, vm);
var result = (await operation.WaitForCompletionAsync()).Value;
Console.WriteLine("VM ID: " + result.Id);
Console.WriteLine("--------Done create VM--------");
}
}
}
Source - https://github.com/Azure-Samples/azure-samples-net-management/blob/master/samples/compute/create-virtual-machine/Program.cs
This sample uses DefaultAzureCredential of Azure.Identity to auth, it will try the following credentials in this doc to auth automatically, one of them is VisualStudioCredential, means it will use the logged user account of your VS. You can also use it directly, replace all the DefaultAzureCredential with VisualStudioCredential in the sample code.
Besides, if you still want to get the token to call the REST API manually, you could use the code below.
var tokenCredential = new VisualStudioCredential();
var accessToken = await tokenCredential.GetTokenAsync("https://management.azure.com");

Send SignIn request to Idp- Sustainsys Saml2

I want to send sign in request for IdP for every unauthenticated request to my Web Application. I am using Sustainsys.Saml2.Owin. I am using the default StubIdp.
In the configuration method, I have added a filter which checks if incoming request is authenticated. If it is not, I issue an OWIN challenge.
My problem is that, the OWIN challenge is not redirecting the application to StubIdp login page. What am I missing here?
Following is the code for Startup.Auth.cs class
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.CreatePerOwinContext(ApplicationDbContext.Create);
app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
// Enable the application to use a cookie to store information for the signed in user
// and to use a cookie to temporarily store information about a user logging in with a third party login provider
// Configure the sign in cookie
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = "MyJobSizeToken",
CookieSecure = CookieSecureOption.Always,
CookieHttpOnly = true,
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
app.UseSaml2Authentication(CreateSaml2Options());
app.Use((context, next) =>
{
if (!context.Authentication.User.Identity.IsAuthenticated)
{
//context.Environment.Add("saml2.idp", new EntityId("https://stubidp.sustainsys.com/Metadata"));
//context.Authentication.Challenge("https://stubidp.sustainsys.com/Metadata");
HttpContext.Current.GetOwinContext().Authentication.Challenge(new
AuthenticationProperties { RedirectUri = "/" },
"KentorAuthServices"); // This line does not redirect to the stubidp
}
return next.Invoke();
});
}
private static Saml2AuthenticationOptions CreateSaml2Options()
{
var spOptions = CreateSPOptions();
var Saml2Options = new Saml2AuthenticationOptions(false)
{
SPOptions = spOptions
};
var idp = new IdentityProvider(new EntityId("https://stubidp.sustainsys.com/Metadata"), spOptions)
{
AllowUnsolicitedAuthnResponse = true,
Binding = Saml2BindingType.HttpRedirect,
SingleSignOnServiceUrl = new Uri("https://stubidp.sustainsys.com")
};
idp.SigningKeys.AddConfiguredKey(
new X509Certificate2(
HostingEnvironment.MapPath(
"~/App_Data/stubidp.sustainsys.com.cer")));
Saml2Options.IdentityProviders.Add(idp);
// It's enough to just create the federation and associate it
// with the options. The federation will load the metadata and
// update the options with any identity providers found.
new Federation("http://localhost:52071/Federation", true, Saml2Options);
return Saml2Options;
}
private static SPOptions CreateSPOptions()
{
var nz = CultureInfo.GetCultureInfo("en-nz");
var organization = new Organization();
organization.Names.Add(new LocalizedName("Flink Solutions", nz));
organization.DisplayNames.Add(new LocalizedName("Flink Solutions", nz));
organization.Urls.Add(new LocalizedUri(new Uri("http://www.Sustainsys.se"), nz));
var spOptions = new SPOptions
{
EntityId = new EntityId("https://localhost:44339/Saml2"),
ReturnUrl = new Uri("http://localhost:44339/"),
DiscoveryServiceUrl = new Uri("http://localhost:44339/DiscoveryService"),
Organization = organization
};
var techContact = new ContactPerson
{
Type = ContactType.Technical
};
techContact.EmailAddresses.Add("Saml2#example.com");
spOptions.Contacts.Add(techContact);
var supportContact = new ContactPerson
{
Type = ContactType.Support
};
supportContact.EmailAddresses.Add("support#example.com");
spOptions.Contacts.Add(supportContact);
var attributeConsumingService = new AttributeConsumingService("Saml2")
{
IsDefault = true,
};
attributeConsumingService.RequestedAttributes.Add(
new RequestedAttribute("urn:someName")
{
FriendlyName = "Some Name",
IsRequired = true,
NameFormat = RequestedAttribute.AttributeNameFormatUri
});
attributeConsumingService.RequestedAttributes.Add(
new RequestedAttribute("Minimal"));
spOptions.AttributeConsumingServices.Add(attributeConsumingService);
spOptions.ServiceCertificates.Add(new X509Certificate2(
AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "/App_Data/Sustainsys.Saml2.Tests.pfx"));
return spOptions;
}
If I issue the request from a cshtml page like;
<script type="text/javascript">
window.location.href = "/Saml2/Signin?idp=https://stubidp.sustainsys.com/Metadata";
</script>
then it works. Why can't I issue the challenge from the OWIN filter?
The second param to Challenge() must be the authentication scheme as set in the Saml2 options. The default is now Saml2, so replace your KentorAuthServices scheme to Saml2.

How to add Owners to an Azure Active Directory Application

I am registering AAD Applications through the following code
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await GetAppTokenAsync());
Application application = new Application()
{
AvailableToOtherTenants = false,
DisplayName = appName,
ErrorUrl = null,
GroupMembershipClaims = null,
Homepage = "http://"+appName,
IdentifierUris = new List<string>() { "https://"+appName },
KeyCredentials = new List<KeyCredential>(),
KnownClientApplications = new List<Guid>(),
LogoutUrl = null,
Oauth2AllowImplicitFlow = false,
Oauth2AllowUrlPathMatching = false,
Oauth2Permissions = new List<OAuth2Permission>(),
Oauth2RequirePostResponse = false,
// PasswordCredentials = new List<PasswordCredential>(),
PasswordCredentials = new List<PasswordCredential>(),
PublicClient = false,
ReplyUrls = new List<string>(),
// RequiredResourceAccess = new List<RequiredResourceAccess>(),
RequiredResourceAccess = new List<RequiredResourceAccess>(),
SamlMetadataUrl = null,
ExtensionProperties = new List<ExtensionProperty>(),
Manager = null,
ObjectType = "Application",
DeletionTimestamp = null,
CreatedOnBehalfOf = null,
CreatedObjects = new List<DirectoryObject>(),
DirectReports = new List<DirectoryObject>(),
Members = new List<DirectoryObject>(),
MemberOf = new List<DirectoryObject>(),
Owners = new List<DirectoryObject>(),
OwnedObjects = new List<DirectoryObject>(),
Policies = new List<DirectoryObject>()
};
I also have an object of type Microsoft.Azure.ActiveDirectory.GraphClient.User which contains all the information of a User that I want to add as owner of the application.
How can I do that?
The way I was trying that is by doing this
activeDirectoryClient.Applications.AddApplicationAsync(application).Wait();
ServicePrincipal newServicePrincpal = new ServicePrincipal();
if (application != null)
{
newServicePrincpal.DisplayName = application.DisplayName;
newServicePrincpal.AccountEnabled = true;
newServicePrincpal.AppId = application.AppId;
newServicePrincpal.Owners.Add(user);
try
{
activeDirectoryClient.ServicePrincipals.AddServicePrincipalAsync(newServicePrincpal).Wait();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
But when I navigate to the application manually in the Azure Portal, the only owner that appears is my own account and not also the other account I got in the user variable
Any idea how to add other owners to the application?
I can reproduce this issue too. The root cause for this issue is that the Azure AD Graph library doesn't provide the owner info when it try to create the service principal.
If you want to add the owner for the service principal, you can use code below after you creating the service principal:
var activeDirectoryClient = GraphHelper.CreateGraphClient();
var sp = (ServicePrincipal)activeDirectoryClient.ServicePrincipals.GetByObjectId("4af8365b-1b49-481c-8c47-7b3fab5611fc").ExecuteAsync().Result;
var user = new Users().GetUserByUserName(activeDirectoryClient, "user2#adfei.onmicrosoft.com").Result;
sp.Owners.Add(user);
sp.UpdateAsync();
And if you want to add the owner of application, here is the code for you reference:
var activeDirectoryClient = GraphHelper.CreateGraphClient();
var app = (Application)activeDirectoryClient.Applications.GetByObjectId("bd87934b-dd4f-446a-a025-7675d1b2464a").ExecuteAsync().Result;
var user = new Users().GetUserByUserName(activeDirectoryClient, "user2#adfei.onmicrosoft.com").Result;
app.Owners.Add(user);
app.UpdateAsync();
More detail about the difference between application and service principal please check this document.
And if you want the Graph client library to support adding the owner when creating the them, you can try to submit the feedback from here.
Update
public static ActiveDirectoryClient CreateGraphClient()
{
string accessToken = "";
string tenantId= "";
string graphResourceId = "https://graph.windows.net";
Uri servicePointUri = new Uri(graphResourceId);
Uri serviceRoot = new Uri(servicePointUri, tenantId);
ActiveDirectoryClient activeDirectoryClient = new ActiveDirectoryClient(serviceRoot, async () => await Task.FromResult(accessToken));
return activeDirectoryClient;
}
Add a runalbe code sample to add an owner for the service principal:
https://github.com/VitorX/AddServicePrincipalWithOwner
Update2
After you run the code sample in the above, you could capture the result using the Fiddler like below. And we can get the object id of service principal via the response of creating the service principal:
Then we can check the owners of principals via the REST like figure below:

Seed identity user table with changed id column?

I have changed the AspNetUser (identity) tabe to only user with the following code.
// Change the name of the table to be Users instead of AspNetUsers
modelBuilder.Entity<IdentityUser>()
.ToTable("Users").Property(p => p.Id).HasColumnName("UserID");
modelBuilder.Entity<ApplicationUser>()
.ToTable("Users").Property(p => p.Id).HasColumnName("UserID");
My application works fine but not my seed. I have this code in Migrations/Configuration.cs
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
var RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
string role = "Admin";
string password = "Password#1234";
//Create Role Admin if it does not exist
if (!RoleManager.RoleExists(role))
{
var roleresult = RoleManager.Create(new IdentityRole(role));
}
//Create Admin users with password=123456
var admin1 = new ApplicationUser();
admin1.UserName = "admin1#admin1.com";
admin1.Email = "admin1#admin1.com";
admin1.EmailConfirmed = true;
UserManager.Create(admin1, password);
UserManager.AddToRole(admin1.Id, role);
context.SaveChanges();
I get the error message "UserId not found". Seems like my UserManager.Create fails.
How can I change my seed code to use the UserID insted of standard Id?
Actually, when you save the user, it does not have an id given yet, you'll have to retrieve the user in between the creation of the user & addToRole:
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
var RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
string role = "Admin";
string password = "Password#1234";
//Create Role Admin if it does not exist
if (!RoleManager.RoleExists(role))
{
var roleresult = RoleManager.Create(new IdentityRole(role));
}
//Create Admin users with password=123456
var admin1 = new ApplicationUser();
admin1.UserName = "admin1#admin1.com";
admin1.Email = "admin1#admin1.com";
admin1.EmailConfirmed = true;
UserManager.Create(admin1, password);
// Refetch user with ID:
dbAdmin1 = context.Users.FirstOrDefault(x => x.UserName == admin1.UserName);
UserManager.AddToRole(dbAdmin1.Id, role);
context.SaveChanges();

The column cannot be modified. [ Column name = UserId ]

I'm relatively new to MVC and SQL CE, and I seem to be having a problem with my primary key field. Inside my database I have 'User' table, and that table has a primary key called 'UserId'. I've created a registration page which gets some data from and should then store that data in the database. However, when the form submit button is clicked, the following error is detected:
The column cannot be modified. [ Column name = UserId ]
This is weird because i'm not attempting to UserId value. The code below is my post action for the registration action:
[HttpPost]
public ActionResult Registration(UserModel user)
{
if (ModelState.IsValid)
{
using (var db = new BlogDbContext())
{
var crypto = new SimpleCrypto.PBKDF2();
var encryptPassword = crypto.Compute(user.Password);
var newUser = db.Users.Create();
newUser.Username = user.Username;
newUser.FirstName = user.FirstName;
newUser.LastName = user.LastName;
newUser.Email = user.Email;
newUser.Password = encryptPassword;
newUser.PasswordSalt = crypto.Salt;
newUser.JoinDate = DateTime.Now;
db.Users.Add(newUser);
db.SaveChanges();
return RedirectToAction("Index", "User");
}
}
else
{
ModelState.AddModelError("", "Registration Failure");
}
return View();
}
When I originally created the database using SQL CE in VS 2012, I set the identity option to 'true' believing that this would auto-increment the primary key column. I've looked on SO for similar questions, however, the majority see to include some sort of SQL query, which I haven't delved into yet. Any help will be greatly appreciated.

Resources