IdentityServer3 icon indicating copy to clipboard operation
IdentityServer3 copied to clipboard

Expose DefaultViewServiceOption list props as IEnumerables.

Open johnkors opened this issue 9 years ago • 5 comments

Fixes #2947. NB! Breaking change & needs a bump of major.

The DefaultViewServiceOption / DefaultViewServiceRegistration allows mutating state on a registred singleton, causing possible bad side effects. This PR changes the option API to use IEnumerable instead of IList - restricting access to modify the list directly.

Example of a bad side effect caused by modifying one of the lists in a class inheriting from the DefaultViewService: https://github.com/IdentityServer/IdentityServer3/issues/2089#issuecomment-222965936

johnkors avatar Jul 11 '16 11:07 johnkors

@johnkors, Thanks for signing the contribution license agreement so quickly! Actual humans will now validate the agreement and then evaluate the PR.
Thanks, DNFBOT;

dnfclas avatar Aug 21 '16 20:08 dnfclas

Ok, will visit once we have a v3 branch. Thx

brockallen avatar Aug 22 '16 00:08 brockallen

Hi all, I am currently experiencing an issue where I modify the config on each request (dynamically adding stylesheets depending on an acr_values) in the constructor of my class (which inherits from DefaultViewService). This feels really messy and I'm not even sure if this is thread safe as my assumption was that the "config" passed to the constructor is factory generated. If anyone can suggest a better method than the following, please:

public class BrandedViewService : DefaultViewService
{
	private static readonly string BrandAcr = "brand";

	public BrandedViewService(DefaultViewServiceOptions config, IViewLoader viewLoader) : base(config, viewLoader)
	{
		// Disable views to re-render styles correctly
		config.CacheViews = false;

		// Remove any branding stylesheets from previous config
		config.Stylesheets = new DefaultViewServiceOptions().Stylesheets;

		// Logic for branding
		var req = HttpContext.Current.Request;
		var env = req.GetOwinContext().Environment;
		var signInId = req["signin"];

		if (signInId == null)
			return;

		var signInMessage = env.GetSignInMessage(signInId);
		var brand = signInMessage?.AcrValues?.FirstOrDefault(acr => acr.StartsWith(BrandAcr + ":", System.StringComparison.Ordinal))?.Substring(BrandAcr.Length + 1);

		if (brand != null)
		{
			config.Stylesheets.Add("~/IdentityServer/Brands/test.css");
		}
	}
}

I am registering the dependency as follows:

factory.ViewService = new DefaultViewServiceRegistration<IdentityServer.BrandedViewService>()
{
	Mode = RegistrationMode.InstancePerHttpRequest
};

I am commenting here because this pull request might solve the issue or at least allow me to do this in a cleaner fashion.

MrJerB avatar Apr 11 '17 07:04 MrJerB

@MrJerB , the work-around for now is to not mutate the singleton-scoped config's lists.

For example:

public class CustomViewService : DefaultViewService 
{
        public CustomViewService(DefaultViewServiceOptions options, IViewLoader viewLoader, OwinEnvironmentService owinservice) : base(options, viewLoader)
        {
            _owinservice = owinservice;
        }

        protected override Task<Stream> Render(CommonViewModel model, string page)
        {
            var acrs = _owinservice.Environment.GetSignInMessage().AcrValues;
            var styleSheets = config.Stylesheets.ToList();
            var isBrandReq = acrs.Any(s => s.Equals("BRAND"));
            if(isBrandReq)
                styleSheets.Add("~/IdentityServer/Brands/test.css");

            return base.Render(model, page, styleSheets, config.Scripts);
        }
}

johnkors avatar Apr 11 '17 09:04 johnkors

Thanks @johnkors and another plus one for showing me how to obtain the OWIN context through dependency injection 👍

MrJerB avatar Apr 11 '17 09:04 MrJerB