Keycloak display custom creation page for custom IDP - keycloak

I try to create a custom IDP for keycloak.
So for test purposes I've written a Provider and a ProviderFactory class both inheriting from their respective SAML counterpart (really only changing the display name and provider ID so far).
public class MySAMLIdentityProviderFactory extends SAMLIdentityProviderFactory {
public static final String PROVIDER_ID = "my-saml";
private DestinationValidator destinationValidator;
#Override
public String getName() {
return "my SAML";
}
#Override
public MySAMLIdentityProvider create(KeycloakSession session, IdentityProviderModel model) {
return new MySAMLIdentityProvider(session, new SAMLIdentityProviderConfig(model), destinationValidator);
}
#Override
public String getId() {
return PROVIDER_ID;
}
#Override
public void init(Scope config) {
super.init(config);
this.destinationValidator = DestinationValidator.forProtocolMap(config.getArray("knownProtocols"));
}
}
public class MySAMLIdentityProvider extends SAMLIdentityProvider {
public MySAMLIdentityProvider(KeycloakSession session, SAMLIdentityProviderConfig config, DestinationValidator destinationValidator) {
super(session, config, destinationValidator);
}
}
Following this advice I also added a realm-identity-provider-my-saml.html and a realm-identity-provider-my-saml-ext.html under ./themes/base/admin/resources/partials.
The -ext being empty and the other html looking like this:
<div data-ng-include data-src="resourceUrl + '/partials/realm-identity-provider-saml.html'"></div>
I can now go to the admin browser console and add this new IDP to my realm.
Uppon creation however, I am not asked for SAML attributes like "Use entity descriptor" or "Display name".
Instead I get the creation screen for social providers asking for "Client-ID" and "Client-Secret".
Only after initial creation, the common SAML attributes appear and can be configured.
How can I get Keycloak to display the propper SAML IDP creation screen?
Edit:
I use Keycloak version 19.0.1.
The issue seems to be connected to the new Keycloak.v2 theme which is weird since as far as I can see the v2 theme does not overwrite the admin resources only the account resources.

So, keycloak has an open issue for this.......

Related

Is there any way to register tenant specific controller in asp.net core using autofac?

I am working on an application built on asp.net core 3.1 + Autofac as DI. Now i want to implement multi tenant feature. I am able to register tenant specific services and it is working as expected.
What i want to achieve is to register tenant specific controller to override default controller registered in main/application level container.
I have registered custom IApplicationFeatureProvider to register only main controllers.
here is the code.
//Startup.cs
public class StartUp
{
void ConfigureServices(IServiceCollection services)
{
services.AddControllers().ConfigureApplicationPartManager(apm =>
{
apm.FeatureProviders.RemoveAt(0); //remove default controller feature provider
apm.FeatureProviders.Add(new MyControllerFeatureProvider()); //register custom provider
}).AddControllersAsServices();
services.AddAutofacMultitenantRequestServices();
//rest of the configuration
}
public void ConfigureContainer(ContainerBuilder builder)
{
//registration of global or main services
builder.RegisterType<MyTenantIdentificationStrategy>()
.As<ITenantIdentificationStrategy>().SingleInstance(); //Get Tenant from request header
builder.Register(container =>
{
ITenantIdentificationStrategy strategy =
container.Resolve<ITenantIdentificationStrategy>();
// tenant resolution code
return new Tenant();
}).InstancePerLifetimeScope();
}
public static MultitenantContainer ConfigureMultitenantContainer(IContainer container)
{
var strategy <-- resolved from container
MultitenantContainer mtc = new MultitenantContainer(strategy, container);
mtc.ConfigureTenant(1, cb => {
cb.RegisterType<IP.Controllers.Extended.HomeController>).InstancePerLifetimeScope();
});
return mtc;
}
}
//HomeController.cs
namespace IP.Controllers
{
[Route("[controller]")]
public class HomeController : ControllerBase
{
}
[Route("Get")
public IActionResult Get()
{
return new JsonResult(new {Main = true});
}
}
//HomeController1.cs
namespace IP.Controllers.Extended <-- different namespace
{
[Route("[controller]")]
public class HomeController : ControllerBase
{
}
[Route("Get")
public IActionResult Get()
{
return new JsonResult(new {Main = false});
}
}
//program.cs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseServiceProviderFactory(new AutofacMultitenantServiceProviderFactory(Startup.ConfigureMultitenantContainer))
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
http://localhost:8082/Home/Get [Without tenantid in header]
http://localhost:8082/Home/Get [Without tenantid=1 in header]
In both case main home controller is resolved not tenant specific..
Any help well be appreciated.
Answer to my own question,
Routing and controller selection is handled Asp.Net Core not by Autofac.
I am registering tenant specific controllers in ConfigureTenant method. Asp.Net Core Framework is not aware about these controllers. So for given route framework always selects registered controller not extended one registered in ConfigureTenant method.
So first i have removed custom application feature provider. Now all controllers in current assembly are registered. Then removed separate registration of controllers from ConfigureTenant method. Now framework throws ambiguity exception because there are two controllers with same route.
From stack trace, i found that EndPointResolver implementation is throwing ambiguity exception. Since we can provide our own implementation for EndPointResolver, i create custom EndPointResolver and register it.
Now during route selection, if there is ambiguity while selecting controller, i just retrieve tenant information from request header and based on Tanent i am handling ambiguity exception.
I am not sure if it is the correct approach but now i am able to register controller with same name [same Route] in different namespace/assembly.

How to initialize several UserProviders in Keycloak

I'm having big difficulties when trying to make Keycloak to use default JPA provider that is implemented in Keycloak (package: org.keycloak.models.jpa.JpaUserProviderFactory)
alongside with my custom UserProvider.
My user provider is implemented like this:
public class MyCustomUserProviderFactory extends JpaUserProviderFactory {
#Override
public String getId() {
return "demo";
}
#Override
public UserProvider create(KeycloakSession session) {
EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
return new MyCustomUserProvider(session, em);
}
}
and MyCustomUserProvider
public class MyCustomUserProvider extends JpaUserProvider {
private final KeycloakSession session;
public MyCustomUserProvider(KeycloakSession session, EntityManager em) {
super(session, em);
this.session = session;
}
}
startup.cli
/subsystem=keycloak-server/spi=user:add(default-provider=demo)
/subsystem=keycloak-server/spi=user/provider=jpa:add(enabled=true)
/subsystem=keycloak-server/spi=user/provider=demo:add(enabled=true)
also in META-INF/service i have
org.keycloak.models.UserProviderFactory
with com.demo.keycloak.providers.user.MyCustomUserProviderFactory
So with this implementation and settings, keycloak is started successfully but when I need to access both demo and jpa UserProvider I only get default provider that is in this case demo.
I access UserProvider with:
UserProvider demoProvider = session.getProvider(UserProvider.class, "demo");
but if I execute
UserProvider jpaProvider = session.getProvider(UserProvider.class, "jpa");
I only get null.
If I change the default provider in the startup.cli (default-provider=demo) I would then get the jpa but not the demo provider.
Can somebody explan to me how could I have several UserProvider implementation and how can I access to both of them?
Sorry for not answering on my question after quite a long time.
Unfortunately there is no way of having multiple JPAUserProvider at the same time.
The only way to add custom implementation in JpaUserProvider methods is to override them add the additional call and in the configuration itself add the custom version as a default one
/subsystem=keycloak-server/spi=user:add(default-provider=demo).

Swagger UI does not list any of the controller/end points though I am able to see the json under v2/api-docs endpoint

I am not able to get my Swagger UI to work with my project. Swagger UI comes up fine but it does not list any of my REST controllers.
I am using SPRING 4.2.6.RELEASE and Swagger 2.5.0 . My rest services are deployed to Tomcat 7.0.54 .
When Tomcat 7.0.54 comes up, it is able to fetch the swagger end points.
I am able to hit the endpoint v2/api-docs that fetches the json messages.
I am also able to hit the swagger-ui but I dont see any controllers listed.
The dropdowns are empty, as below
**The issue I am facing currently is that
I am not able to fetch the /swagger-resources/configuration/ui, when I launch the swagger UI I get 404 (Not Found) errror while the UI is trying to fetch /swagger-resources/configuration/ui . I have setup resource handlers for swagger-resources, but that does not seem to help. Can you please let me know what could be missing?
Should I be seeing resources folder under META-INF in my expanded WAR? Should there be any springfox related files/folder inside META-INF?
**
Maven dependency for Swagger
io.springfox
springfox-swagger2
2.5.0
io.springfox
springfox-swagger-ui
2.5.0
Below is my SwaggerCongifuration
#EnableSwagger2
public class SwaggerConfiguration {
#Bean
public Docket api() {
List<SecurityContext> security = new ArrayList<SecurityContext>();
security.add(securityContext());
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.pathMapping("/").securityContexts(security);
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.forPaths(PathSelectors.regex("/"))
.build();
}
}
Below is my WebConfig.xml
#EnableWebMvc
#Configuration
#Import(SwaggerConfiguration.class)
#ComponentScan("com.bank.direct.services")
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> pConverters) {
pConverters.add(RestUtils.getJSONMessageConverter());
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Below is the SecurityCongif.xml
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationService _authenticationService;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder pAuth) throws Exception {
pAuth.userDetailsService(_authenticationService);
}
#Override
protected void configure(HttpSecurity pHttp) throws Exception {
// Enable HTTP caching
pHttp.headers().cacheControl().disable();
// Configure security
pHttp.httpBasic()
// -- Allow only authenticated request
.and()
.authorizeRequests()
.anyRequest().authenticated()
// -- Logout configuration
.and()
.logout()
.logoutUrl("/rest/users/logout/")
.deleteCookies("XSRF-TOKEN")
.logoutSuccessUrl("/static/index.html")
.invalidateHttpSession(true)
// -- CSRF configuration
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
Rest Controller class as below
#RestController
#RequestMapping(value = "/vehicles", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class VehicleResource extends Resource {
#Autowired
private IVehicleService _vehicleService;
#RequestMapping(value = "/brands", method = RequestMethod.GET)
public APIResponseEntity getBrands(WebRequest pWebRequest) {
IUser user = getUser(pWebRequest);
BrandCriteria criteria = new BrandCriteria();
criteria.setLanguageCode(user.getLanguageCode());
List<Brand> res = _vehicleService.getBrands(user, criteria);
return newResponseOK(res);
}
#RequestMapping(value = "/brands/{brand_code}", method = RequestMethod.GET)
public APIResponseEntity getBrand(WebRequest pWebRequest, #PathVariable("brand_code") String pBrandCode) {
IUser user = getUser(pWebRequest);
BrandCriteria criteria = new BrandCriteria();
criteria.setLanguageCode(user.getLanguageCode());
criteria.setBrandCode(pBrandCode);
List<Brand> res = _vehicleService.getBrands(user, criteria);
return newResponseOK(res);
}
}
After migrating an older project from XML Spring configuration to Java Spring configuration and updating spring and Swagger versions I struggled with an issue that sounds exactly like this so I thought I'd document my solution here.
I had a number of problems but the main ones that match the OP's scenario were that while /v2/api-docs was accessible and returned JSON, my Controllers clearly weren't being picked up, and when I accessed the Swagger UI at /swagger-ui.html, I was getting a 404 when that page tried to request /swagger-resources/configuration/ui
My Swagger configuration class was:
#Configuration
#EnableSwagger2
public class SwaggerWebConfig {
#Bean
public Docket api() {
...
}
}
The #EnableSwagger2 annotation imports another configuration class Swagger2DocumentationConfiguration, which in turn imports SwaggerCommonConfiguration, which does a component scan for classes in springfox.documentation.swagger.web which finally loads the ApiResourceController, which is where
/swagger-resources/
/swagger-resources/configuration/security and
/swagger-resources/configuration/ui
are served from.
What I had incorrect was that my SwaggerWebConfig class was being loaded by the root application context, when it should belong to the web application context (see ApplicationContext vs WebApplicationContext).
Beans in the web application context can access beans in the root application context, but not the other way around, which explained why Docket bean (incorrectly in the root application context) could not pick up the #Controller beans in the web application context and also explained why despite the ApiResourceController bean being created, its methods were giving 404's when trying to access them (they should be in the web application context)
A few other notes for related issues:
If you can hit v2/api-docs then your Docket bean is working
In a non-spring-boot environment, you need to register two resource handlers yourself as spring boot's auto-configuration would have done this for you as explained in the answers to this question. That should solve 404's for:
/swagger-ui.html (i.e. 404 fetching the actual html swagger-ui.html page)
and the three webjars that swagger-ui.html loads:
/webjars/springfox-swagger-ui/springfox.js
/webjars/springfox-swagger-ui/swagger-ui-bundle.js
/webjars/springfox-swagger-ui/swagger-ui-standalone-preset.js
If you are getting an access denied rather than a 404 not found, then as shown in this answer, you might need to tell spring security to allow access to:
/webjars/**
/swagger-ui.html
/v2/api-docs
/swagger-resources/**
You need to point the the generated Swagger Definition in Swagger UI. i.e in place of http://example.com/api give your swagger definition path something like http://localhost:8080/RestResource/api/swagger.json
This article might help you more

how can I get the value of " #{readeredit.reader.tel}" form the jsf form? [duplicate]

I have started learning JSF, but sadly most tutorials out there present only a log in or a register section.
Can you point me to some more in depth examples? One thing I'm interested in is a page presenting a list of products. I'm on page home and I press on page products so that I can see the latest products added. And every time I visit the page, the product list will be created from the latest entries in the database. How can I handle this?
One way to solve this would be to create a session scoped managed bean in which I would place different entities updated through other managed beans. I found this kind of approach in some tutorials, but it seems quite difficult and clumsy.
Which would be the best approach to solve a thing like this? What is the correct usage of session scope in two-page master-detail user interface?
What is the correct usage of session scope
Use it for session scoped data only, nothing else. For example, the logged-in user, its settings, the chosen language, etcetera.
See also:
How to choose the right bean scope?
And every time I visit the page, the product list will be created from the latest entries in the database. How can I handle this?
Typically you use the request or view scope for it. Loading of the list should happen in a #PostConstruct method. If the page doesn't contain any <h:form>, then the request scope is fine. A view scoped bean would behave like a request scoped when there's no <h:form> anyway.
All "view product" and "edit product" links/buttons which just retrieve information (i.e. idempotent) whould be just plain GET <h:link> / <h:button> wherein you pass the entity identifier as a request parameter by <f:param>.
All "delete product" and "save product" links/buttons which will manipulate information (i.e. non-idempotent) should perform POST by <h:commandLink>/<h:commandButton> (you don't want them to be bookmarkable/searchbot-indexable!). This in turn requires a <h:form>. In order to preserve the data for validations and ajax requests (so that you don't need to reload/preinitialize the entity on every request), the bean should preferably be view scoped.
Note that you should basically have a separate bean for each view and also note that those beans doesn't necessarily need to reference each other.
So, given this "product" entity:
#Entity
public class Product {
#Id
private Long id;
private String name;
private String description;
// ...
}
And this "product service" EJB:
#Stateless
public class ProductService {
#PersistenceContext
private EntityManager em;
public Product find(Long id) {
return em.find(Product.class, id);
}
public List<Product> list() {
return em.createQuery("SELECT p FROM Product p", Product.class).getResultList();
}
public void create(Product product) {
em.persist(product);
}
public void update(Product product) {
em.merge(product);
}
public void delete(Product product) {
em.remove(em.contains(product) ? product : em.merge(product));
}
// ...
}
You can have this "view products" on /products.xhtml:
<h:dataTable value="#{viewProducts.products}" var="product">
<h:column>#{product.id}</h:column>
<h:column>#{product.name}</h:column>
<h:column>#{product.description}</h:column>
<h:column>
<h:link value="Edit" outcome="/products/edit">
<f:param name="id" value="#{product.id}" />
</h:link>
</h:column>
</h:dataTable>
#Named
#RequestScoped
public class ViewProducts {
private List<Product> products; // +getter
#EJB
private ProductService productService;
#PostConstruct
public void init() {
products = productService.list();
}
// ...
}
And you can have this "edit product" on /products/edit.xhtml:
<f:metadata>
<f:viewParam name="id" value="#{editProduct.product}"
converter="#{productConverter}" converterMessage="Unknown product, please use a link from within the system."
required="true" requiredMessage="Bad request, please use a link from within the system."
/>
</f:metadata>
<h:messages />
<h:form rendered="#{not empty editProduct.product}>
<h:inputText value="#{editProduct.product.name}" />
<h:inputTextarea value="#{editProduct.product.description}" />
...
<h:commandButton value="save" action="#{editProduct.save}" />
</h:form>
#Named
#ViewScoped
public class EditProduct {
private Product product; // +getter +setter
#EJB
private ProductService productService;
public String save() {
productService.update(product);
return "/products?faces-redirect=true";
}
// ...
}
And this converter for <f:viewParam> of "edit product":
#Named
#RequestScoped
public class ProductConverter implements Converter {
#EJB
private ProductService productService;
#Override
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if (value == null || value.isEmpty()) {
return null;
}
try {
Long id = Long.valueOf(value);
return productService.find(id);
} catch (NumberFormatException e) {
throw new ConverterException("The value is not a valid Product ID: " + value, e);
}
}
#Override
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (value == null) {
return "";
}
if (value instanceof Product) {
Long id = ((Product) value).getId();
return (id != null) ? String.valueOf(id) : null;
} else {
throw new ConverterException("The value is not a valid Product instance: " + value);
}
}
}
You can even use a generic converter, this is explained in Implement converters for entities with Java Generics.
See also:
How to navigate in JSF? How to make URL reflect current page (and not previous one)
JSF Controller, Service and DAO
JSF Service Layer
How to inject #EJB, #PersistenceContext, #Inject, #Autowired, etc in #FacesConverter?
Communication in JSF 2.0 - Contains several examples/hints
As a small improvement to what BalusC recommended, sometimes you can remove the required / requiredMessage part from the <f:viewParam> of your "details" screen and instead use the conditional rendering of the editing form (as BalusC did) with a reverse condition for recommending a specific link for the "list/master" screen or, even use a viewAction that would test the param and force a redirect to that list.

GWT Getting Google User's Name After Successful OAuth Login

In my GWT application, I am using the com.google.api.gwt.oauth2.OAuth2 module to allow users to signin with OAuth. I have implemented a GoogleApi class to provide the OAuth login as follows:
public final class GoogleApi {
private EventBus eventBus;
private final ClientOAuth2Login oAuth2Login;
private ClientGoogleApiRequestTransport requestTransport;
private String accessToken;
public GoogleApi(final EventBus eventBus, String clientId) {
this.eventBus = eventBus;
oAuth2Login = new ClientOAuth2Login(clientId);
}
...
public void login(final Receiver<String> callback) {
oAuth2Login.login(new Receiver<String>() {
#Override
public void onSuccess(String response) {
accessToken = response;
callback.onSuccess(response);
}
#Override
public void onFailure(ServerFailure error) {
Window.alert(error.getMessage());
}
});
}
...
}
I am calling this login() method as follows:
googleApi.login(new Receiver<String>() {
#Override
public void onSuccess(final String token) {
// I want to get the logged in user's name here
}
#Override
public void onFailure(ServerFailure error) {
...
}
});
After successful login, I want to get the logged in user's full name. I know that I can get the user's name using the com.google.api.gwt.services.Plus module, but that requires users to be registered to Google Plus and throws an error if they're not.
How can I do this using OAuth in my GWT app? Basically, I want to be able to implement this solution in a GWT app, if possible.
Have you see this? I thought it was quite useful. It is, however, a lot of code to achieve a simple result:
http://www.sw-engineering-candies.com/blog-1/howtogetuserinformationwithoauth2inagwtandgoogleappenginejavaapplication