How to create a user using microstrategy SDK in java (External Security Module) - microstrategy

My goal is to create user using microstrategy SDK and assign filters and groups to the user.
I have a java class CreateUser & SSOESM from SDK. Do I have to create a plugin of the create user class and deploy it in microstrategy intelligence server.
public class CreateUser {
public static WebIServerSession sessionInfo;
public static final String loginName = "NewUser";
public static final String password= "";
public static final String fullName= "New User";
public static final String description="New User Created Programattically";
/* The following information is required to login and manipulate the User management API */
/* iServerName is the IServer we are connecting to */
public static final String iServerName = "localhost";
/* projectName is the project name we are connecting to */
public static final String projectName = "";
/* loginName is the user name we use to login the project */
public static final String adminLoginName = "administrator";
/* loginPasswd is the password we use to login the project */
public static final String adminLoginPasswd = "";
public static void main(String[] args) {
sessionInfo = getServerSession(iServerName, projectName, adminLoginName, adminLoginPasswd);
UserBean user = null;
try {
//Instantiate a new user
user = (UserBean)BeanFactory.getInstance().newBean("UserBean");
user.setSessionInfo(sessionInfo);
user.InitAsNew();
//Fetch properties for the user
WebUser ua = (WebUser) user.getUserEntityObject();
WebStandardLoginInfo loginInfo = ua.getStandardLoginInfo();
//Set basic user information
ua.setLoginName(loginName);
ua.setFullName(fullName);
user.getObjectInfo().setDescription(description);
loginInfo.setPassword(password);
//Set other properties
Calendar cal = Calendar.getInstance();
cal.set(2012, 11, 21);
Date d = cal.getTime();
loginInfo.setPasswordExpirationDate(d); //Password expires on November 21, 2012
loginInfo.setPasswordExpirationFrequency(90); //90 days to expire
loginInfo.setPasswordExpiresAutomatically(true); //If set to false, password never expires
loginInfo.setStandardAuthAllowed(true); //The user can log in using standard auth
user.save();
} catch (WebBeanException ex) {
System.out.println("Error creating a user: " + ex.getMessage());
}
}
public static WebIServerSession getServerSession(String serverName, String Project, String loginName, String password) {
WebIServerSession sessionInfo = null;
try {
WebObjectsFactory woFact = WebObjectsFactory.getInstance();
sessionInfo = woFact.getIServerSession();
sessionInfo.setServerName(serverName);
sessionInfo.setProjectName(Project);
sessionInfo.setLogin(loginName);
sessionInfo.setPassword(password);
sessionInfo.setApplicationType(EnumDSSXMLApplicationType.DssXmlApplicationCustomApp);
//Create a new session
sessionInfo.getSessionID();
} catch (WebObjectsException ex) {
System.out.println("Error creating a sesion");
}
return sessionInfo;
}
}
My goal is when a user try to logon the user should be created on the fly using the sdk classes.

I have to create a plugin and configure the plugin to use the java class you have created as an ESM.
https://lw.microstrategy.com/msdz/MSDZ_World2015/docs/projects/WebSDK/output/HTML5/Content/topics/esm/specifying_the_custom_esm_to_use.htm
With that said its important to understand that the actions you are performing are very expensive. They may degrade the user experience if you are attempting to provide a fast SSO experience. Depending on the implementation you have it may be better to create a custom task, which can be fired when the user authenticates with the third party application. This task can perform all the actions you are describing, and then return a session state. Which can be used in any subsequent connections to MicroStrategy Web.

Related

Keycloak OTP for read only federated users

I have implemented a custom user storage provider for federating users from our database.
I want to manage OTP for those users via keycloak, when I set the OTP to required in the flow and Configure OTP as required action the otp form is shown after federated user login, but when I try to setup the OTP I receive the error user is read only for this update.
How can I allow read only federated users to allow OTP configuration via keycloak?
2022-01-31 17:00:12,704 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-669) Uncaught server error: org.keycloak.storage.ReadOnlyException: user is read only for this update
at org.keycloak.keycloak-server-spi#15.1.1//org.keycloak.storage.adapter.AbstractUserAdapter.removeRequiredAction(AbstractUserAdapter.java:77)
at org.keycloak.keycloak-services#15.1.1//org.keycloak.services.resources.LoginActionsService.processRequireAction(LoginActionsService.java:1044)
at org.keycloak.keycloak-services#15.1.1//org.keycloak.services.resources.LoginActionsService.requiredActionPOST(LoginActionsService.java:967)
the user adapter is
public class UserAdminAdapter extends AbstractUserAdapter {
private final CustomUser user;
public UserAdminAdapter(
KeycloakSession session,
RealmModel realm,
ComponentModel storageProviderModel,
CustomUser user) {
super(session, realm, storageProviderModel);
this.user = user;
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public Stream<String> getAttributeStream(String name) {
Map<String, List<String>> attributes = getAttributes();
return (attributes.containsKey(name)) ? attributes.get(name).stream() : Stream.empty();
}
#Override
protected Set<GroupModel> getGroupsInternal() {
if (user.getGroups() != null) {
return user.getGroups().stream().map(UserGroupModel::new).collect(Collectors.toSet());
}
return new HashSet<>();
}
#Override
protected Set<RoleModel> getRoleMappingsInternal() {
if (user.getRoles() != null) {
return user.getRoles().stream().map(roleName -> new UserRoleModel(roleName, realm)).collect(Collectors.toSet());
}
return new HashSet<>();
}
#Override
public boolean isEnabled() {
return user.isEnabled();
}
#Override
public String getId() {
return StorageId.keycloakId(storageProviderModel, user.getUserId() + "");
}
#Override
public String getFirstAttribute(String name) {
List<String> list = getAttributes().getOrDefault(name, Collections.emptyList());
return list.isEmpty() ? null : list.get(0);
}
#Override
public Map<String, List<String>> getAttributes() {
MultivaluedHashMap<String, String> attributes = new MultivaluedHashMap<>();
attributes.add(UserModel.USERNAME, getUsername());
attributes.add(UserModel.EMAIL, getEmail());
attributes.add(UserModel.FIRST_NAME, getFirstName());
attributes.add(UserModel.LAST_NAME, getLastName());
attributes.addAll(user.getAttributes());
return attributes;
}
#Override
public String getFirstName() {
return user.getFirstName();
}
#Override
public String getLastName() {
return user.getLastName();
}
#Override
public String getEmail() {
return user.getEmail();
}
}
The reason is that in your UserAdminAdapter class, you have not implemented the removeRequiredAction and addRequiredAction methods. The message you're receiving is from the default implementation provided by the base class. You should either implement these methods yourself and store the required actions in your underlying storage, OR consider extending your class from AbstractUserAdapterFederatedStorage instead which delegates all such functionalities to the internal Keycloak implementation.
FULL OTP support in my external DB
Well, finally after more than a week I got this working with Keycloak 18.0. What do you need to do?, simply, you have to implement each and every step in the authentication workflow:
Create your user storage SPI
Implement Credential Update SPI
Implement a custom Credential Provider SPI
Implement a custom Required Action SPI
Implement your authenticator SPI
Implement your forms (I kinda used the internal OTP forms in KC)
Enable your Required action
Create a copy of the browser workflow and plaster there your authenticator
And what do we get with this?
We get a fully customizable OTP authenticator (realm's policy pending...)
You can use that code for verification in your app (it's in your db)
You can setup users for OTP authentication in your app (no KC admin page involved, so, you can leave the admin page outside the firewall)
In my opinion, this is kinda annoying, since there are a lot of loops we have to make to be able to store our data locally and how to deal with the integrated OTP forms (for a "natural look"), but it gives me full control over my OTP integration, also, I can backup my database and their OTP authentication is still there, so, if I have a failure in a KC upgrade or it gets corrupted, I still have all that data.
Lastly, heres what it should look like when your manager has the custom OTP authentication

Keycloak - read-only user attributes

I want to keep some information in Keycloak as custom user attributes.
Some of them should be managed by the user itself. Other attributes should be managed only by a Keycloak administrator. Attributes managed by the administrator should be read-only visible in the "Edit account" web page for the user.
I went through the guide to add custom user attributes in this page and customized the "Edit account" web page.
My question is:
Is it ensured that the user cannot change the attribute that is meant as read-only for the user? E.g. by submitting a form where he/she sends correct data that will be automatically mapped on the server side to the user attribute.
For what you've said, it seems that you have three choices.
One would be to keep the keycloak "Edit Account" page and use an update profile listener to check what attributes are stored or which ones are updated by who, something like this:
public class UpdateProfile implements RequiredActionProvider, RequiredActionFactory, DisplayTypeRequiredActionFactory {
#Override
public InitiatedActionSupport initiatedActionSupport() {
return InitiatedActionSupport.SUPPORTED;
}
#Override
public void evaluateTriggers(RequiredActionContext context) {
}
#Override
public void requiredActionChallenge(RequiredActionContext context) {
Response challenge = context.form()
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
}
// Check the custom attribute 1 not being modified by the user
#Override
public void processAction(RequiredActionContext context) {
EventBuilder event = context.getEvent();
event.event(EventType.UPDATE_PROFILE);
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
UserModel user = context.getUser();
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1")
if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
Response challenge = context.form()
.setError("User cannot change the attribute")
.setFormData(formData)
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
return;
}
context.success();
}
#Override
public void close() {
}
#Override
public RequiredActionProvider create(KeycloakSession session) {
return this;
}
#Override
public RequiredActionProvider createDisplay(KeycloakSession session, String displayType) {
if (displayType == null) return this;
if (!OAuth2Constants.DISPLAY_CONSOLE.equalsIgnoreCase(displayType)) return null;
return ConsoleUpdateProfile.SINGLETON;
}
#Override
public void init(Config.Scope config) {
}
#Override
public void postInit(KeycloakSessionFactory factory) {
}
#Override
public String getDisplayText() {
return "Update Profile";
}
#Override
public String getId() {
return UserModel.RequiredAction.UPDATE_PROFILE.name();
}
}
What I don't know is if this listener will be called when you update the profile from your client application too. If it gets called, you'll need to check which is the logged in client, if it's the public client do not let update the attributes, if it's your service client, let it.
The second one would be to only let your service client update the user profiles and make a custom view in your application which sends a form POST to your client, instead of to keycloak directly. This way you can validate it in the service before sending it to keycloak.
The third one is to implement a FormAction interface, which would allow you to validate the incoming form at server side:
The core interface you have to implement is the FormAction interface. A FormAction is responsible for rendering and processing a portion of the page. Rendering is done in the buildPage() method, validation is done in the validate() method, post validation operations are done in success().
#Override
public void validate(ValidationContext context) {
MultivaluedMap<String, String> formData = context.getHttpRequest().getDecodedFormParameters();
UserModel user = context.getUser();
KeycloakSession session = context.getSession();
RealmModel realm = context.getRealm();
String newYourCustomAttribute1 = formData.getFirst("yourCustomAttribute1");
String oldYourCustomAttribute1 = user.getFirstAttribute("yourCustomAttribute1")
if (!newYourCustomAttribute1.equals(oldYourCustomAttribute1)) {
Response challenge = context.form()
.setError("User cannot change the attribute")
.setFormData(formData)
.createResponse(UserModel.RequiredAction.UPDATE_PROFILE);
context.challenge(challenge);
return;
}
context.success();
}
perform an update to version 12.0.4.
There were some issues < 12.0.4 with dropping all attributes if user updates his profile.
Additionally with 12.0.4 you can create user- and admin-read only attributes.
Check documentation: https://www.keycloak.org/docs/latest/server_admin/#_read_only_user_attributes
Cheers

CognitoAuthentication Extension for .NET with Unity (StartWithSrpAuthAsync()) issue

After importing the AWS-SDK for .NET dll's including the AWS.Extension.CognitoAuthentication into Unity 2018.2, I am having a problem with the StartWithSrpAuthAsync function taken from AuthenticateWithSrpAsync provided by https://aws.amazon.com/blogs/developer/cognitoauthentication-extension-library-developer-preview/
Code from site:
public async void AuthenticateWithSrpAsync()
{
var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(),
FallbackRegionFactory.GetRegionEndpoint());
CognitoUserPool userPool = new CognitoUserPool("poolID", "clientID", provider);
CognitoUser user = new CognitoUser("username", "clientID", userPool, provider);
string password = "userPassword";
AuthFlowResponse context = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
{
Password = password
}).ConfigureAwait(false);
}
}
I want a button script to take in a username and password from the user and authenticate it with the UserPool I created in Cognito.
Button Script
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Amazon;
using Amazon.Runtime;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;
public class test : MonoBehaviour {
public string userName;
public string userPassword;
public string clientID;
public string poolID;
public AuthFlowResponse authResponse;
public CognitoUserPool userPool;
public AmazonCognitoIdentityProviderClient provider;
public CognitoUser user;
void Start()
{
}
public void OnClick()
{
try
{
AuthenticateWithSrpAsync();
}
catch(Exception ex)
{
Debug.Log(ex);
}
}
public async void AuthenticateWithSrpAsync()
{
RegionEndpoint CognitoIdentityRegion = RegionEndpoint.USEast1;
provider = new AmazonCognitoIdentityProviderClient(null, CognitoIdentityRegion);
userPool = new CognitoUserPool(poolID, clientID, provider, null);
user = new CognitoUser(userName, clientID, userPool, provider);
string name = user.Username.ToString();
Debug.Log(name);
authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() {
Password = userPassword
}).ConfigureAwait(false);
Debug.Log(user.SessionTokens.IdToken);
Debug.Log("Success");
}
}
The app client does not require a secret key.
App Client
https://imgur.com/a/NUzBghb
The User status is confirmed/enabled and the email is verified.
User
https://imgur.com/lsnG5tT
What ends up happening is the script runs until it gets to:
authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest() {
Password = userPassword
}).ConfigureAwait(false);
Debug.Log(user.SessionTokens.IdToken);
Debug.Log("Success");
And does absolutely nothing afterwards. Neither of the debugs show in the console as well as any Error or warning messages.
Unity Console:
https://imgur.com/Hxpcmoj
I have looked through the StackOverflow questions as well as every other resource I could find on google. I have also replicated this in Unity 2017.3
I'm using .NetFramework 4.6
Try checking the checkbox "Enable username-password(non-SRP)...shown in the image in the link https://imgur.com/a/NUzBghb.
I don't think it is related to Unity as the same code is working fine at my end. Can you try following:
_provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), );

How to authenticate with Azure AD / OpenId but use Entity Framework based user/role data

I'm trying to improve the authentication story for a legacy ASPNet MVC/OWIN app - Currently, it uses the AspNetUsers / AspNetRoles / claims etc tables along with forms + cookie based authentication.
I want to use Azure AD / OpenID Connect for authentication but then load the user profile/roles from the database as currently. Basically, no more password management within the app. Users themselves will still need to exist/be created within the app.
The application is quite dependent on some custom data associated with these users so simply using the roles from Active Directory isn't an option.
The OpenID auth works, however I'm not sure how to use the existing Identityuser / IdentityUserRole / RoleManager plumbing in conjunction with it.
Basically once the user authenticates with Open ID we'll want to load the corresponding user from the database (matching on email address) and use that user profile / roles going forward.
In particular, the AuthorizeAttribute (with specific roles specified) should continue to function as before.
This is what I have so far:
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(AppIdentityDbContext.Create);
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
ConfigureAuth(app);
}
/// <summary>
/// Configures OpenIDConnect Authentication & Adds Custom Application Authorization Logic on User Login.
/// </summary>
/// <param name="app">The application represented by a <see cref="IAppBuilder"/> object.</param>
private void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
//Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigHelper.ClientId,
Authority = String.Format(CultureInfo.InvariantCulture, ConfigHelper.AadInstance,
ConfigHelper.Tenant), // For Single-Tenant
PostLogoutRedirectUri = ConfigHelper.PostLogoutRedirectUri,
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
RoleClaimType = "roles",
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/OtherError?errorDescription=" +
context.Exception.Message);
return Task.FromResult(0);
},
SecurityTokenValidated = async context =>
{
string userIdentityName = context.AuthenticationTicket.Identity.Name;
var userManager = context.OwinContext.GetUserManager<AppUserManager>();
var user = userManager.FindByEmail(userIdentityName);
if (user == null)
{
Log.Error("User {name} authenticated with open ID, but unable to find matching user in store", userIdentityName);
context.HandleResponse();
context.Response.Redirect("/Error/NoAccess?identity=" + userIdentityName);
return;
}
user.DateLastLogin = DateTime.Now;
IdentityResult result = await userManager.UpdateAsync(user);
if (result.Succeeded)
{
var authManager = context.OwinContext.Authentication;
ClaimsIdentity ident = await userManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ExternalBearer);
// Attach additional claims from DB user
authManager.User.AddIdentity(ident);
// authManager.SignOut();
// authManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);
return;
}
throw new Exception(string.Format("Failed to update user {0} after log-in", userIdentityName));
}
}
});
}
}
Here's what I ended up doing:
public class IdentityConfig
{
public void Configuration(IAppBuilder app)
{
app.CreatePerOwinContext(AppIdentityDbContext.Create);
app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
app.CreatePerOwinContext<AppRoleManager>(AppRoleManager.Create);
ConfigureAuth(app);
}
/// <summary>
/// Configures OpenIDConnect Authentication & Adds Custom Application Authorization Logic on User Login.
/// </summary>
/// <param name="app">The application represented by a <see cref="IAppBuilder"/> object.</param>
private void ConfigureAuth(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieDomain = ConfigHelper.AuthCookieDomain,
SlidingExpiration = true,
ExpireTimeSpan = TimeSpan.FromHours(2)
});
//Configure OpenIDConnect, register callbacks for OpenIDConnect Notifications
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
ClientId = ConfigHelper.ClientId,
Authority = String.Format(CultureInfo.InvariantCulture, ConfigHelper.AadInstance, ConfigHelper.Tenant),
TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
{
RoleClaimType = ClaimTypes.Role
},
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.Redirect("/Error/OtherError?errorDescription=" + context.Exception.Message);
return Task.FromResult(0);
},
RedirectToIdentityProvider = context =>
{
// Set the post-logout & redirect URI dynamically depending on the incoming request.
// That allows us to use the same Azure AD app for two subdomains (these two domains give different app behaviour)
var builder = new UriBuilder(context.Request.Uri);
builder.Fragment = builder.Path = builder.Query = "";
context.ProtocolMessage.PostLogoutRedirectUri = builder.ToString();
context.ProtocolMessage.RedirectUri = builder.ToString();
return Task.FromResult(0);
}
}
});
app.Use<EnrichIdentityWithAppUserClaims>();
}
}
public class EnrichIdentityWithAppUserClaims : OwinMiddleware
{
public EnrichIdentityWithAppUserClaims(OwinMiddleware next) : base(next)
{
}
public override async Task Invoke(IOwinContext context)
{
await MaybeEnrichIdentity(context);
await Next.Invoke(context);
}
private async Task MaybeEnrichIdentity(IOwinContext context)
{
ClaimsIdentity openIdUserIdentity = (ClaimsIdentity)context.Authentication.User.Identity;
string userIdentityName = openIdUserIdentity.Name;
var userManager = context.GetUserManager<AppUserManager>();
var appUser = userManager.FindByEmail(userIdentityName);
if (appUser == null)
{
Log.Error("User {name} authenticated with open ID, but unable to find matching user in store", userIdentityName);
return;
}
appUser.DateLastLogin = DateTime.Now;
IdentityResult result = await userManager.UpdateAsync(appUser);
if (result.Succeeded)
{
ClaimsIdentity appUserIdentity = await userManager.CreateIdentityAsync(appUser, DefaultAuthenticationTypes.ExternalBearer);
openIdUserIdentity.AddClaims(appUserIdentity.Claims);
}
}
}
It's quite similar to what I had originally - (note: RoleClaimType = ClaimTypesRoles not "roles") except instead of trying to deal with the user in the SecurityTokenValidated callback, I've added some custom middleware that finds a matching user (by email address) and adds the claims (app roles) from the matching app user to the authenticated user identity (OpenID identity).
Finally, I protected all controller actions with a (custom) AuthorizeAttribute (not shown here) that ensures the authenticated user at least belongs to the "User" role (if not, redirects them to a "no access" page indicating that we've authenticated them but they have no access in the system).
The OpenID auth works, however I'm not sure how to use the existing Identityuser / IdentityUserRole / RoleManager plumbing in conjunction with it.
The application is quite dependent on some custom data associated with these users so simply using the roles from Active Directory isn't an option.
For your requirement, I assume that you could build your identity server (e.g. IdentityServer3) and leverage IdentityServer3.AspNetIdentity for identity management using ASP.NET Identity.
For your web client application, you could use OpenID Connect middleware and set Authority to your custom identity server and set your pre-configured ClientId on your identity server.
Moreover, you could follow this tutorial for quickly getting started with IdentityServer3 and the full samples IdentityServer3 Samples.

Setting realm and issuer for web service at Runtime (windows azure ACS)

We have the scenario in our project,
We have Tenant-1 to Tenant-n which consume Restful Service S1. The tenants have a 1 to 1 relationship with IDP. Client has to federate the tenant UI through Restful Service using ACS with the help of tenant specific IDP configured in ACS at the time of onboarding.
Tenant-1 mapped to IdP1 (Eg: Yahoo)
Tenant-2 mapped to Idp2 (Eg: Google)
Restful Service returns a JavaScript as JSON, which is hosted within the Tenant’s Web UI. So if the tenant has already logged on to the Tenant UI using IDP specific to him via his own application, then for any requests from the tenant UI to Restful Service, the Restful service should federate to tenant specific IdP based on the partner information (mapping of tenant to IdP) configured during the onboarding process.
I am setting Realm in the Global.asax as shown below.
public class WebApiApplication : System.Web.HttpApplication
{
public event EventHandler RedirectingToIdentityProvider;
public override void Init()
{
FederatedAuthentication.WSFederationAuthenticationModule.RedirectingToIdentityProvider += WSFederationAuthenticationModule_RedirectingToIdentityProvider;
}
void WSFederationAuthenticationModule_RedirectingToIdentityProvider(object sender, RedirectingToIdentityProviderEventArgs e)
{
Tenant tenant = GetTenantDetails(subId); // Gets the tenant information from MetaData based on subscriptionId
if (tenant != null)
{
e.SignInRequestMessage.Realm = tenant.Realm + "CMS/";
}
}
protected void Application_Start()
{
FederatedAuthentication.FederationConfigurationCreated += OnServiceConfigurationCreated;
}
private void OnServiceConfigurationCreated(object sender, FederationConfigurationCreatedEventArgs e)
{
if (tenant != null)
{
e.FederationConfiguration.WsFederationConfiguration.Issuer = tenant.Issuer;
Uri uri = new Uri(tenant.Realm + "CMS/");
if (!e.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Contains(uri))
e.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(tenant.Realm + "CMS/"));
e.FederationConfiguration.WsFederationConfiguration.Realm = tenant.Realm + "CMS/";
}
}
Further the Realm is set at the per request level too, as shown below.
public class MetaDataModule : IHttpModule
{
private static string WSFederationAuthenticationModuleName = string.Empty;
public void Init(HttpApplication httpContextApplication)
{
var requestWrapper = new EventHandler(DoSyncRequestWorkToGetTenantDetails);
httpContextApplication.BeginRequest += requestWrapper;
}
private static void DoSyncRequestWorkToGetTenantDetails(object sender, EventArgs e)
{
var httpContextApplication = (HttpApplication)sender;
Tenant tenant = GetTenantDetails(); // Gets the tenant information from MetaData based on subscriptionId
if (tenant != null)
{
WSFederationAuthenticationModule wsfed = (WSFederationAuthenticationModule)httpContextApplication.Modules["WSFederationAuthenticationModule"];
wsfed.FederationConfiguration.WsFederationConfiguration.Issuer = tenant.Issuer;
Uri uri = new Uri(tenant.Realm + "CMS/");
if (!wsfed.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Contains(uri))
wsfed.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(tenant.Realm + "CMS/"));
wsfed.FederationConfiguration.WsFederationConfiguration.Realm = tenant.Realm + "CMS/";
//FederatedAuthentication.FederationConfiguration.WsFederationConfiguration.Issuer = tenant.Issuer;
//Uri uri = new Uri(tenant.Realm + "CMS/");
//if (!FederatedAuthentication.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Contains(uri))
// FederatedAuthentication.FederationConfiguration.IdentityConfiguration.AudienceRestriction.AllowedAudienceUris.Add(new Uri(tenant.Realm + "CMS/"));
//FederatedAuthentication.FederationConfiguration.WsFederationConfiguration.Realm = tenant.Realm + "CMS/";
}
}
Please find the modules registered in the Web.config and the remaining part of WIF configuration too.
In spite of resetting the Realm for each request, the new value does not get assigned.
Client does not want their tenants to implement any authentication or federation related code from their end for this to work.
Please let me know if you can think of any solution to this issue with the help of Passive Federation.
You should customize the realm in the Application_AuthenticateRequest method in your Global.asax.
Take a look at this link.