First I want to say I'm not SL developer. I just need to modify one legacy Silverlight 5 application.
It is using RIA services and XAP is hosted in Asp.Net page.
User on login page enters credentials and is able to select database from dropdown. Whole web is using multiple connections and user is able to select database to connect.
This selected database (or any identificator for data connection) is sent do XAP's InitParams, so I can access it from SL.
private void Application_Startup(object sender, StartupEventArgs e)
{
foreach (var item in e.InitParams)
{
Resources.Add(item.Key, item.Value);
}
var selectedConnectionString = GetInitParam("ConnectionString");
// TODO: Different way to store connection string
SetCookie("ConnectionString", selectedConnectionString);
RootVisual = new LoadingPage();
}
Currently I'm trying to use cookie to store selected database. I found it somewhere as one possible solution. But it needs to change.
Ok, then we have DomainService.
public class CommissionDomainService : LinqToEntitiesDomainService<CommissionEntitiesContext>
{
...
}
I know that I need to use CreateObjectContext to change ConnectionString in service. So I have:
protected override CommissionEntitiesContext CreateObjectContext()
{
// TODO: Different way to store connection string
string connectionStringName;
if (System.Web.HttpContext.Current.Request.Cookies["ConnectionString"] != null)
{
connectionStringName = System.Web.HttpContext.Current.Request.Cookies["ConnectionString"].Value;
}
else
{
throw new Exception("Missing connectionStringName");
}
var connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName];
var entityCs = new EntityConnectionStringBuilder
{
Metadata = "res://*/CommissionEntities.csdl|res://*/CommissionEntities.ssdl|res://*/CommissionEntities.msl",
Provider = connectionStringSettings.ProviderName,
ProviderConnectionString = connectionStringSettings.ConnectionString
};
return new CommissionEntitiesContext(entityCs.ConnectionString);
}
Again, I used Cookie to pass value from application to service.
But it is not the best idea. Because of cookie and because of persistance etc.
My question is, how to pass my ConnectionString value from main application to DomainService? Or Can I access some application context from service? Or maybe can I get connection string somewhere in EntitiesContext?
Ok, I did it this way.
I made selected database part of user identity. Because I'm using Owin, I just used one of Claims.
So when user logs in, I just put one claim with selected database
// build a list of claims
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Name),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.UserData, selectedDatabase)
};
// create the identity
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);
// sign in
Context.GetOwinContext().Authentication.SignIn(new AuthenticationProperties { IsPersistent = false }, identity);
Then in DomainService I used Initialize and CreateObjectContext methods
private string _connectionStringName;
public override void Initialize(DomainServiceContext context)
{
// Načteme z kontextu usera zvolenou databázi
var claim = ((ClaimsIdentity)context.User.Identity).FindFirst(ClaimTypes.UserData);
_connectionStringName = claim.Value;
base.Initialize(context);
...
}
protected override CommissionEntitiesContext CreateObjectContext()
{
if (string.IsNullOrEmpty(_connectionStringName))
{
throw new Exception("Missing connectionStringName");
}
var connectionStringSettings = ConfigurationManager.ConnectionStrings[_connectionStringName];
var entityCs = new EntityConnectionStringBuilder
{
Metadata = "res://*/CommissionEntities.csdl|res://*/CommissionEntities.ssdl|res://*/CommissionEntities.msl",
Provider = connectionStringSettings.ProviderName,
ProviderConnectionString = connectionStringSettings.ConnectionString
};
return new CommissionEntitiesContext(entityCs.ConnectionString);
}
Related
I have a multitenant application that I'm restricting access to specific tenants. The code to authorize a user from specific tenants works fine and for those without access I'm throwing a SecurityTokenInvalidIssuerException. The problem is I don't know how to capture this error because the code is executed at the start of Program.cs and prior to the app.build. How do I gracefully handle this error? ...right now it's just crashing the app.
Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
//builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
// .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration);
// Restrict users to specific belonging to specific tenants
UserValidator user = new UserValidator();
builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.TokenValidationParameters.IssuerValidator = user.ValidateSpecificIssuers;
});
UserValidator.cs
public class UserValidator
{
public string ValidateSpecificIssuers(string issuer, SecurityToken securityToken,
TokenValidationParameters validationParameters)
{
var validIssuers = GetAcceptedTenantIds()
.Select(tid => $"https://login.microsoftonline.com/{tid}/v2.0");
if (validIssuers.Contains(issuer))
{
return issuer;
}
else
{
throw new SecurityTokenInvalidIssuerException("The sign-in user's account does not belong to one of the tenants that this Web App accepts users from.");
}
}
private string[] GetAcceptedTenantIds()
{
// If you are an ISV who wants to make the Web app available only to certain customers who
// are paying for the service, you might want to fetch this list of accepted tenant ids from
// a database.
// Here for simplicity we just return a hard-coded list of TenantIds.
var config = new ConfigurationBuilder()
.SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
.AddJsonFile("appsettings.json").Build();
var acceptedTenants = config.GetSection("Tenants").GetChildren();
string[] tenants = new string[acceptedTenants.Count()];
int i = 0;
foreach (var acceptedTenant in acceptedTenants)
{
tenants[i] = acceptedTenant.GetValue<string>("TenantId");
i++;
}
return tenants;
}
}
I am creating an application and have made two tables but want to create tables of AspNetRoles, AspNetUsers, AspNetUsersRoles etc tables by code first approach. How would it create without using DB first approach or manually making tables
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
createRolesandUsers();
}
private void createRolesandUsers()
{
ApplicationDbContext context = new ApplicationDbContext();
var roleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(context));
var UserManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
// In Startup iam creating first Admin Role and creating a default Admin User
if (!roleManager.RoleExists("SuperAdmin"))
{
// first we create Admin rool
var role = new Microsoft.AspNet.Identity.EntityFramework.IdentityRole();
role.Name = "SuperAdmin";
roleManager.Create(role);
//Here we create a Admin super user who will maintain the website
var user = new ApplicationUser();
user.UserName = "abc";
user.Email = "abc#gmail.com";
string userPWD = "password";
var chkUser = UserManager.Create(user, userPWD);
//Add default User to Role Admin
if (chkUser.Succeeded)
{
var result1 = UserManager.AddToRole(user.Id, "SuperAdmin");
}
}
//creating Creating Manager role
if (!roleManager.RoleExists("Manager"))
{
var role = new Microsoft.AspNet.Identity.EntityFramework.IdentityRole();
role.Name = "Manager";
roleManager.Create(role);
}
// creating Creating Employee role
if (!roleManager.RoleExists("Employee"))
{
var role = new Microsoft.AspNet.Identity.EntityFramework.IdentityRole();
role.Name = "Employee";
roleManager.Create(role);
}
}
I have done like this and have not done else
If I got your question right you just need to extend IdentityDbContext which is part of Identity Framework and generate a migration. This is a way to do code first approach.
https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli
I've been trying to setup a new IdentityServer3 with AspNetIdentity for a few days now. I'm able to login using my existing Identity DB and that's all good but I can never get the User.Identity.Name to contain data.
I've tried multiple attempts at adding custom claims & scopes and adding scopes to clients.
Finally, I loaded up the IdentityServer3 Sample repository and tested it out with the webforms client project since it already used the User.Identity.Name in it's About page.
Using WebForms sample client + AspNetIdentity sample server = User.Identity.Name is always null
Using WebForms sample client + SelfHost with Seq sample server = User.Identity.Name with data
I've tried other sample host projects that all populate the User.Identity.Name value just fine.
Now, on the client side I've written a workaround to pull the 'preferred_username' claim value and set the 'name' claim with it.
var id = new claimsIdentity(n.AuthenticationTicket.Identity.AuthenticationType);
id.AddClaims(userInfoResponse.GetClaimsIdentity().Claims);
//set the User.Identity.Name value
var name = id.Claims.Where(x => x.Type == "name").Select(x => x.Value).FirstOrDefault() ??
id.Claims.Where(x => x.Type == "preferred_username").Select(x => x.Value).FirstOrDefault();
id.AddClaim(new Claim("name", name));
My questions are:
Why doesn't the AspNetIdentity package fill this by default?
And what do I need to change on the server side so that I don't need to change the client?
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]
{
new ApiResource("MyApi", "My Admin API")
{
UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.Email }
}
};
}
In Identityserver4 you can add the UserClaims to your resource. Fixed it for me.
On IdentityServer4 you can implement IProfileService on server and add the Claim in GetProfileDataAsync
public class AspNetIdentityProfileService : IProfileService
{
protected UserManager<ApplicationUser> _userManager;
public AspNetIdentityProfileService(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName),
};
context.IssuedClaims.AddRange(claims);
//Return
return Task.FromResult(0);
}
public Task IsActiveAsync(IsActiveContext context)
{
//Processing
var user = _userManager.GetUserAsync(context.Subject).Result;
context.IsActive = (user != null) && ((!user.LockoutEnd.HasValue) || (user.LockoutEnd.Value <= DateTime.Now));
//Return
return Task.FromResult(0);
}
}
Then add "AddProfileService()" to your ConfigureServices method.
services.AddIdentityServer(...)
...
.AddProfileService<AspNetIdentityProfileService>();
I have written a couple of ms lightswitch applications with forms authentication -> this creates aspnet_* tables in sql server.
How can I use the defined users, passwords, maybe even memberships, roles and application rights in a servicestack - application?
I have not tested this but I think it should get you started. Gladly stand corrected on any of my steps.
Things I think you will need to do..
In order to Authenticate against both 'systems' you'll need to set the Forms cookie and save your ServiceStack session.
Instead of calling FormsAuthentication.Authentiate() do something like below. This won't work until you complete all the steps.
var apiAuthService = AppHostBase.Resolve<AuthService>();
apiAuthService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var apiResponse = apiAuthService.Authenticate(new Auth
{
UserName = model.UserName,
Password = model.Password,
RememberMe = false
});
Create a subclass of IUserAuthRepository (for retrieving membership/user/roles from aspnet_* tables and filling ServiceStack AuthUser).
CustomAuthRepository.cs (incomplete, but should get you started)
public class CustomAuthRepository : IUserAuthRepository
{
private readonly MembershipProvider _membershipProvider;
private readonly RoleProvider _roleProvider;
public CustomAuthRepository()
{
_membershipProvider = Membership.Provider;
_roleProvider = Roles.Provider;
}
public UserAuth GetUserAuthByUserName(string userNameOrEmail)
{
var user = _membershipProvider.GetUser(userNameOrEmail, true);
return new UserAuth {FirstName = user.UserName, Roles = _roleProvider.GetRolesForUser(userNameOrEmail).ToList() //FILL IN REST OF PROPERTIES};
}
public bool TryAuthenticate(string userName, string password, out UserAuth userAuth)
{
//userId = null;
userAuth = GetUserAuthByUserName(userName);
if (userAuth == null) return false;
if (FormsAuthentication.Authenticate(userName, password))
{
FormsAuthentication.SetAuthCookie(userName, false);
return true;
}
userAuth = null;
return false;
}
//MORE METHODS TO IMPLEMENT...
}
Wire Authentication up for ServiceStack in AppHost configure method.
var userRep = new CustomAuthRepository();
container.Register<IUserAuthRepository>(userRep);
Plugins.Add(
new AuthFeature(() => new AuthUserSession(),
new IAuthProvider[] {
new CredentialsAuthProvider()
}
));
I am using windows Authentication and accessing user name as.
IIdentity winId = HttpContext.Current.User.Identity;
string name = winId.Name;
but i want to get other details like User full name and EmailID.
Since you're on a windows network, then you need to query the Active directory to search for user and then get it's properties such as the email
Here is an example function DisplayUser that given an IIdentity on a windows authenticated network, finds the user's email:
public static void Main() {
DisplayUser(WindowsIdentity.GetCurrent());
Console.ReadKey();
}
public static void DisplayUser(IIdentity id) {
WindowsIdentity winId = id as WindowsIdentity;
if (id == null) {
Console.WriteLine("Identity is not a windows identity");
return;
}
string userInQuestion = winId.Name.Split('\\')[1];
string myDomain = winId.Name.Split('\\')[0]; // this is the domain that the user is in
// the account that this program runs in should be authenticated in there
DirectoryEntry entry = new DirectoryEntry("LDAP://" + myDomain);
DirectorySearcher adSearcher = new DirectorySearcher(entry);
adSearcher.SearchScope = SearchScope.Subtree;
adSearcher.Filter = "(&(objectClass=user)(samaccountname=" + userInQuestion + "))";
SearchResult userObject = adSearcher.FindOne();
if (userObject != null) {
string[] props = new string[] { "title", "mail" };
foreach (string prop in props) {
Console.WriteLine("{0} : {1}", prop, userObject.Properties[prop][0]);
}
}
}
gives this:
Edit: If you get 'bad user/password errors'
The account that the code runs under must have access the users domain. If you run code in asp.net then the web application must be run under an application pool with credentials with domain access. See here for more information
You can define a MyCustomIdentity by overriding from IIdentity and add your own properties etc.
Cast it to the specific Identity, for example WindowsIdentity