org.keycloak.adapters.spi.HttpFacade Java Examples

The following examples show how to use org.keycloak.adapters.spi.HttpFacade. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: SpringSecurityRequestAuthenticatorTest.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    request = spy(new MockHttpServletRequest());
    response = new MockHttpServletResponse();
    HttpFacade facade = new SimpleHttpFacade(request, response);

    authenticator = new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, 443);

    // mocks
    when(principal.getKeycloakSecurityContext()).thenReturn(refreshableKeycloakSecurityContext);

    when(refreshableKeycloakSecurityContext.getDeployment()).thenReturn(deployment);
    when(refreshableKeycloakSecurityContext.getToken()).thenReturn(accessToken);

    when(accessToken.getRealmAccess()).thenReturn(access);
    when(access.getRoles()).thenReturn(Sets.newSet("user", "admin"));

    when(deployment.isUseResourceRoleMappings()).thenReturn(false);
}
 
Example #2
Source File: BundleBasedKeycloakConfigResolver.java    From keycloak with Apache License 2.0 6 votes vote down vote up
protected KeycloakDeployment findDeployment(HttpFacade.Request request) {
    if (bundleContext == null) {
        throw new IllegalStateException("bundleContext must be set for BundleBasedKeycloakConfigResolver!");
    }

    URL url = bundleContext.getBundle().getResource(configLocation);
    if (url == null) {
        throw new IllegalStateException("Failed to find the file " + configLocation + " on classpath.");
    }

    try {
        InputStream is = url.openStream();
        return KeycloakDeploymentBuilder.build(is);
    } catch (IOException ioe) {
        throw new IllegalStateException("Error reading file' " + configLocation + "' from bundle classpath.", ioe);
    }
}
 
Example #3
Source File: BearerTokenRequestAuthenticator.java    From keycloak with Apache License 2.0 6 votes vote down vote up
public AuthOutcome authenticate(HttpFacade exchange)  {
    List<String> authHeaders = exchange.getRequest().getHeaders("Authorization");
    if (authHeaders == null || authHeaders.isEmpty()) {
        challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
        return AuthOutcome.NOT_ATTEMPTED;
    }

    tokenString = null;
    for (String authHeader : authHeaders) {
        String[] split = authHeader.trim().split("\\s+");
        if (split.length != 2) continue;
        if (split[0].equalsIgnoreCase("Bearer")) {
            tokenString = split[1];

            log.debugf("Found [%d] values in authorization header, selecting the first value for Bearer.", (Integer) authHeaders.size());
            break;
        }
    }

    if (tokenString == null) {
        challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.NO_BEARER_TOKEN, null, null);
        return AuthOutcome.NOT_ATTEMPTED;
    }

    return (authenticateToken(exchange, tokenString));
}
 
Example #4
Source File: ClaimInformationPointProviderTest.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Test
public void testHttpClaimInformationPointProviderWithClaims() {
    HttpFacade httpFacade = createHttpFacade();

    Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-post-claim-provider", "http").resolve(httpFacade);

    assertEquals("a-value1", claims.get("claim-a").get(0));
    assertEquals("d-value1", claims.get("claim-d").get(0));
    assertEquals("d-value2", claims.get("claim-d").get(1));
    assertEquals("d-value1", claims.get("claim-d0").get(0));
    assertEquals("d-value1", claims.get("claim-d-all").get(0));
    assertEquals("d-value2", claims.get("claim-d-all").get(1));

    assertNull(claims.get("a"));
    assertNull(claims.get("b"));
    assertNull(claims.get("d"));
}
 
Example #5
Source File: ClaimInformationPointProviderTest.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Test
public void testHttpClaimInformationPointProviderWithoutClaims() {
    HttpFacade httpFacade = createHttpFacade();

    Map<String, List<String>> claims = getClaimInformationProviderForPath("/http-get-claim-provider", "http").resolve(httpFacade);

    assertEquals("a-value1", claims.get("a").get(0));
    assertEquals("b-value1", claims.get("b").get(0));
    assertEquals("d-value1", claims.get("d").get(0));
    assertEquals("d-value2", claims.get("d").get(1));

    assertNull(claims.get("claim-a"));
    assertNull(claims.get("claim-d"));
    assertNull(claims.get("claim-d0"));
    assertNull(claims.get("claim-d-all"));
}
 
Example #6
Source File: HttpClaimInformationPointProvider.java    From keycloak with Apache License 2.0 6 votes vote down vote up
private void setHeaders(RequestBuilder builder, HttpFacade httpFacade) {
    Object headersDef = config.get("headers");

    if (headersDef != null) {
        Map<String, Object> headers = Map.class.cast(headersDef);

        for (Entry<String, Object> header : headers.entrySet()) {
            Object value = header.getValue();
            List<String> headerValues = new ArrayList<>();

            if (value instanceof Collection) {
                Collection values = Collection.class.cast(value);

                for (Object item : values) {
                    headerValues.addAll(PlaceHolders.resolve(item.toString(), httpFacade));
                }
            } else {
                headerValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
            }

            for (String headerValue : headerValues) {
                builder.addHeader(header.getKey(), headerValue);
            }
        }
    }
}
 
Example #7
Source File: HttpClaimInformationPointProvider.java    From keycloak with Apache License 2.0 6 votes vote down vote up
private void setParameters(RequestBuilder builder, HttpFacade httpFacade) {
    Object config = this.config.get("parameters");

    if (config != null) {
        Map<String, Object> paramsDef = Map.class.cast(config);

        for (Entry<String, Object> paramDef : paramsDef.entrySet()) {
            Object value = paramDef.getValue();
            List<String> paramValues = new ArrayList<>();

            if (value instanceof Collection) {
                Collection values = Collection.class.cast(value);

                for (Object item : values) {
                    paramValues.addAll(PlaceHolders.resolve(item.toString(), httpFacade));
                }
            } else {
                paramValues.addAll(PlaceHolders.resolve(value.toString(), httpFacade));
            }

            for (String paramValue : paramValues) {
                builder.addParameter(paramDef.getKey(), paramValue);
            }
        }
    }
}
 
Example #8
Source File: KeycloakAdapterPolicyEnforcer.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Override
protected boolean challenge(PathConfig pathConfig, PolicyEnforcerConfig.MethodConfig methodConfig, OIDCHttpFacade httpFacade) {
    if (isBearerAuthorization(httpFacade)) {
        HttpFacade.Response response = httpFacade.getResponse();
        AuthzClient authzClient = getAuthzClient();
        String ticket = getPermissionTicket(pathConfig, methodConfig, authzClient, httpFacade);

        if (ticket != null) {
            response.setStatus(401);
            response.setHeader("WWW-Authenticate", new StringBuilder("UMA realm=\"").append(authzClient.getConfiguration().getRealm()).append("\"").append(",as_uri=\"")
                    .append(authzClient.getServerConfiguration().getIssuer()).append("\"").append(",ticket=\"").append(ticket).append("\"").toString());
        } else {
            response.setStatus(403);
        }

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Sending challenge");
        }

        return true;
    }

    handleAccessDenied(httpFacade);

    return true;
}
 
Example #9
Source File: ClaimInformationPointProviderTest.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Test
public void testBasicClaimsInformationPoint() {
    HttpFacade httpFacade = createHttpFacade();
    Map<String, List<String>> claims = getClaimInformationProviderForPath("/claims-provider", "claims").resolve(httpFacade);

    assertEquals("parameter-a", claims.get("claim-from-request-parameter").get(0));
    assertEquals("header-b", claims.get("claim-from-header").get(0));
    assertEquals("cookie-c", claims.get("claim-from-cookie").get(0));
    assertEquals("user-remote-addr", claims.get("claim-from-remoteAddr").get(0));
    assertEquals("GET", claims.get("claim-from-method").get(0));
    assertEquals("/app/request-uri", claims.get("claim-from-uri").get(0));
    assertEquals("/request-relative-path", claims.get("claim-from-relativePath").get(0));
    assertEquals("true", claims.get("claim-from-secure").get(0));
    assertEquals("static value", claims.get("claim-from-static-value").get(0));
    assertEquals("static", claims.get("claim-from-multiple-static-value").get(0));
    assertEquals("value", claims.get("claim-from-multiple-static-value").get(1));
    assertEquals("Test param-other-claims-value1 and parameter-a", claims.get("param-replace-multiple-placeholder").get(0));
}
 
Example #10
Source File: MultiTenantResolver.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Override
public KeycloakDeployment resolve(HttpFacade.Request request) {

    String path = request.getURI();
    int multitenantIndex = path.indexOf("multi-tenant/");
    if (multitenantIndex == -1) {
        throw new IllegalStateException("Not able to resolve realm from the request path!");
    }

    String realm = path.substring(path.indexOf("multi-tenant/")).split("/")[1];
    if (realm.contains("?")) {
        realm = realm.split("\\?")[0];
    }
    
    InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("/" + realm + "-keycloak.json");

    if (is == null) {
        throw new IllegalStateException("Not able to find the file /" + realm + "-keycloak.json");
    }

    KeycloakDeployment deployment = KeycloakDeploymentBuilder.build(is);
    return deployment;
}
 
Example #11
Source File: PlaceHolders.java    From keycloak with Apache License 2.0 6 votes vote down vote up
private static Map<String, List<String>> parsePlaceHolders(String value, HttpFacade httpFacade) {
    Map<String, List<String>> placeHolders = new HashMap<>();
    Matcher matcher = PLACEHOLDER_PATTERN.matcher(value);

    while (matcher.find()) {
        String placeHolder = matcher.group(1);
        int resolverNameIdx = placeHolder.indexOf('.');

        if (resolverNameIdx == -1) {
            throw new RuntimeException("Invalid placeholder [" + value + "]. Could not find resolver name.");
        }

        PlaceHolderResolver resolver = resolvers.get(placeHolder.substring(0, resolverNameIdx));

        if (resolver != null) {
            List<String> resolved = resolver.resolve(placeHolder, httpFacade);

            if (resolved != null) {
                placeHolders.put(formatPlaceHolder(placeHolder), resolved);
            }
        }
    }

    return placeHolders;
}
 
Example #12
Source File: SamlUtil.java    From keycloak with Apache License 2.0 6 votes vote down vote up
/**
 * Gets a url to redirect to if there is an IDP initiated login.  Looks for a redirectTo query param first, then looks
 * in RelayState, if not in either defaults to context path.
 *
 * @param facade
 * @param contextPath
 * @param baseUri
 * @return
 */
public static String getRedirectTo(HttpFacade facade, String contextPath, String baseUri) {
    String redirectTo = facade.getRequest().getQueryParamValue("redirectTo");
    if (redirectTo != null && !redirectTo.isEmpty()) {
        return buildRedirectTo(baseUri, redirectTo);
    } else {
        redirectTo = facade.getRequest().getFirstParam(GeneralConstants.RELAY_STATE);
        if (redirectTo != null) {
            int index = redirectTo.indexOf("redirectTo=");
            if (index >= 0) {
                String to = redirectTo.substring(index + "redirectTo=".length());
                index = to.indexOf(';');
                if (index >=0) {
                    to = to.substring(0, index);
                }
                return buildRedirectTo(baseUri, to);
            }
        }
        if (contextPath.isEmpty()) baseUri += "/";
        return baseUri;
    }
}
 
Example #13
Source File: OAuthRequestAuthenticator.java    From keycloak with Apache License 2.0 6 votes vote down vote up
protected AuthChallenge loginRedirect() {
    final String state = getStateCode();
    final String redirect =  getRedirectUri(state);
    if (redirect == null) {
        return challenge(403, OIDCAuthenticationError.Reason.NO_REDIRECT_URI, null);
    }
    return new AuthChallenge() {

        @Override
        public int getResponseCode() {
            return 0;
        }

        @Override
        public boolean challenge(HttpFacade exchange) {
            tokenStore.saveRequest();
            log.debug("Sending redirect to login page: " + redirect);
            exchange.getResponse().setStatus(302);
            exchange.getResponse().setCookie(deployment.getStateCookieName(), state, "/", null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
            exchange.getResponse().setHeader("Location", redirect);
            return true;
        }
    };
}
 
Example #14
Source File: KeycloakPreAuthActionsFilter.java    From keycloak with Apache License 2.0 6 votes vote down vote up
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
        throws IOException, ServletException {

    HttpFacade facade = new SimpleHttpFacade((HttpServletRequest)request, (HttpServletResponse)response);
    KeycloakDeployment deployment = deploymentContext.resolveDeployment(facade);
    
    if (deployment == null) {
        return;
    }

    if (deployment.isConfigured()) {
        nodesRegistrationManagement.tryRegister(deploymentContext.resolveDeployment(facade));
    }

    PreAuthActionsHandler handler = preAuthActionsHandlerFactory.createPreAuthActionsHandler(facade);
    if (handler.handleRequest()) {
        log.debug("Pre-auth filter handled request: {}", ((HttpServletRequest) request).getRequestURI());
    } else {
        chain.doFilter(request, response);
    }
}
 
Example #15
Source File: PlaceHolders.java    From keycloak with Apache License 2.0 6 votes vote down vote up
public static List<String> resolve(String value, HttpFacade httpFacade) {
    Map<String, List<String>> placeHolders = parsePlaceHolders(value, httpFacade);

    if (!placeHolders.isEmpty()) {
        value = formatPlaceHolder(value);

        for (Entry<String, List<String>> entry : placeHolders.entrySet()) {
            List<String> values = entry.getValue();

            if (values.isEmpty() || values.size() > 1) {
                return values;
            }

            value = value.replaceAll(entry.getKey(), values.get(0)).trim();
        }
    }

    return Arrays.asList(value);
}
 
Example #16
Source File: CookieTokenStore.java    From keycloak with Apache License 2.0 5 votes vote down vote up
static String getContextPath(HttpFacade facade) {
    String uri = facade.getRequest().getURI();
    String path = KeycloakUriBuilder.fromUri(uri).getPath();
    if (path == null || path.isEmpty()) {
        return "/";
    }
    int index = path.indexOf("/", 1);
    return index == -1 ? path : path.substring(0, index);
}
 
Example #17
Source File: BundleBasedKeycloakConfigResolver.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public KeycloakDeployment resolve(HttpFacade.Request request) {
    if (cachedDeployment != null) {
        return cachedDeployment;
    } else {
        cachedDeployment = findDeployment(request);
        return cachedDeployment;
    }
}
 
Example #18
Source File: OfflineAccessPortalServlet.java    From keycloak with Apache License 2.0 5 votes vote down vote up
private KeycloakDeployment getDeployment(HttpServletRequest servletRequest) throws ServletException {
    // The facade object is needed just if you have relative "auth-server-url" in keycloak.json. Otherwise you can call deploymentContext.resolveDeployment(null)
    HttpFacade facade = getFacade(servletRequest);

    AdapterDeploymentContext deploymentContext = (AdapterDeploymentContext) getServletContext().getAttribute(AdapterDeploymentContext.class.getName());
    if (deploymentContext == null) {
        throw new ServletException("AdapterDeploymentContext not set");
    }
    return deploymentContext.resolveDeployment(facade);
}
 
Example #19
Source File: KeycloakAuthenticationEntryPointTest.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Before
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    authenticationEntryPoint = new KeycloakAuthenticationEntryPoint(adapterDeploymentContext);
    request = new MockHttpServletRequest();
    response = new MockHttpServletResponse();
    when(applicationContext.getBean(eq(AdapterDeploymentContext.class))).thenReturn(adapterDeploymentContext);
    when(adapterDeploymentContext.resolveDeployment(any(HttpFacade.class))).thenReturn(keycloakDeployment);
    when(keycloakDeployment.isBearerOnly()).thenReturn(Boolean.FALSE);
}
 
Example #20
Source File: AlfrescoBearerTokenRequestAuthenticator.java    From alfresco-repository with GNU Lesser General Public License v3.0 5 votes vote down vote up
@Override
protected AuthChallenge challengeResponse(HttpFacade facade, Reason reason, String error, String description)
{
    this.validationFailureDescription = description;
    
    return super.challengeResponse(facade, reason, error, description);
}
 
Example #21
Source File: AdapterDeploymentContext.java    From keycloak with Apache License 2.0 5 votes vote down vote up
protected KeycloakDeployment resolveUrls(KeycloakDeployment deployment, HttpFacade facade) {
    if (deployment.relativeUrls == RelativeUrlsUsed.NEVER) {
        // Absolute URI are already set to everything
        return deployment;
    } else {
        DeploymentDelegate delegate = new DeploymentDelegate(this.deployment);
        delegate.setAuthServerBaseUrl(getBaseBuilder(facade, this.deployment.getAuthServerBaseUrl()).build().toString());
        return delegate;
    }
}
 
Example #22
Source File: BearerTokenRequestAuthenticator.java    From keycloak with Apache License 2.0 5 votes vote down vote up
protected AuthChallenge clientCertChallenge() {
    return new AuthChallenge() {
        @Override
        public int getResponseCode() {
            return 0;
        }

        @Override
        public boolean challenge(HttpFacade exchange) {
            // do the same thing as client cert auth
            return false;
        }
    };
}
 
Example #23
Source File: CookieTokenStore.java    From keycloak with Apache License 2.0 5 votes vote down vote up
public static void setTokenCookie(KeycloakDeployment deployment, HttpFacade facade, RefreshableKeycloakSecurityContext session) {
    log.debugf("Set new %s cookie now", AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE);
    String accessToken = session.getTokenString();
    String idToken = session.getIdTokenString();
    String refreshToken = session.getRefreshToken();
    String cookie = new StringBuilder(accessToken).append(DELIM)
            .append(idToken).append(DELIM)
            .append(refreshToken).toString();

    String cookiePath = getCookiePath(deployment, facade);
    facade.getResponse().setCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookie, cookiePath, null, -1, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true);
}
 
Example #24
Source File: CookieTokenStore.java    From keycloak with Apache License 2.0 5 votes vote down vote up
static String getCookiePath(KeycloakDeployment deployment, HttpFacade facade) {
    String path = deployment.getAdapterStateCookiePath() == null ? "" : deployment.getAdapterStateCookiePath().trim();
    if (path.startsWith("/")) {
        return path;
    }
    String contextPath = getContextPath(facade);
    StringBuilder cookiePath = new StringBuilder(contextPath);
    if (!contextPath.endsWith("/") && !path.isEmpty()) {
        cookiePath.append("/");
    }
    return cookiePath.append(path).toString();
}
 
Example #25
Source File: KeycloakAuthenticationEntryPoint.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    HttpFacade facade = new SimpleHttpFacade(request, response);
    if (apiRequestMatcher.matches(request) || adapterDeploymentContext.resolveDeployment(facade).isBearerOnly()) {
        commenceUnauthorizedResponse(request, response);
    } else {
        commenceLoginRedirect(request, response);
    }
}
 
Example #26
Source File: AbstractKeycloakAuthenticatorValve.java    From keycloak with Apache License 2.0 5 votes vote down vote up
protected AdapterTokenStore getTokenStore(Request request, HttpFacade facade, KeycloakDeployment resolvedDeployment) {
    AdapterTokenStore store = (AdapterTokenStore)request.getNote(TOKEN_STORE_NOTE);
    if (store != null) {
        return store;
    }

    if (resolvedDeployment.getTokenStore() == TokenStore.SESSION) {
        store = createSessionTokenStore(request, resolvedDeployment);
    } else {
        store = new CatalinaCookieTokenStore(request, facade, resolvedDeployment, createPrincipalFactory());
    }

    request.setNote(TOKEN_STORE_NOTE, store);
    return store;
}
 
Example #27
Source File: RequestAuthenticator.java    From keycloak with Apache License 2.0 5 votes vote down vote up
protected boolean isAutodetectedBearerOnly(HttpFacade.Request request) {
    if (!deployment.isAutodetectBearerOnly()) return false;

    String headerValue = facade.getRequest().getHeader("X-Requested-With");
    if (headerValue != null && headerValue.equalsIgnoreCase("XMLHttpRequest")) {
        return true;
    }

    headerValue = facade.getRequest().getHeader("Faces-Request");
    if (headerValue != null && headerValue.startsWith("partial/")) {
        return true;
    }

    headerValue = facade.getRequest().getHeader("SOAPAction");
    if (headerValue != null) {
        return true;
    }

    List<String> accepts = facade.getRequest().getHeaders("Accept");
    if (accepts == null) accepts = Collections.emptyList();

    for (String accept : accepts) {
        if (accept.contains("text/html") || accept.contains("text/*") || accept.contains("*/*")) {
            return false;
        }
    }

    return true;
}
 
Example #28
Source File: AbstractPolicyEnforcer.java    From keycloak with Apache License 2.0 5 votes vote down vote up
private void resolveClaims(Map<String, List<String>> claims, Map<String, Map<String, Object>> claimInformationPointConfig, HttpFacade httpFacade) {
    if (claimInformationPointConfig != null) {
        for (Entry<String, Map<String, Object>> claimDef : claimInformationPointConfig.entrySet()) {
            ClaimInformationPointProviderFactory factory = getPolicyEnforcer().getClaimInformationPointProviderFactories().get(claimDef.getKey());

            if (factory != null) {
                claims.putAll(factory.create(claimDef.getValue()).resolve(httpFacade));
            }
        }
    }
}
 
Example #29
Source File: KeycloakAdapterPolicyEnforcer.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
protected void handleAccessDenied(OIDCHttpFacade facade) {
    String accessDeniedPath = getEnforcerConfig().getOnDenyRedirectTo();
    HttpFacade.Response response = facade.getResponse();

    if (accessDeniedPath != null) {
        response.setStatus(302);
        response.setHeader("Location", accessDeniedPath);
    } else {
        response.sendError(403);
    }
}
 
Example #30
Source File: KeycloakSecurityContextPlaceHolderResolver.java    From keycloak with Apache License 2.0 5 votes vote down vote up
@Override
public List<String> resolve(String placeHolder, HttpFacade httpFacade) {
    String source = placeHolder.substring(placeHolder.indexOf('.') + 1);
    OIDCHttpFacade oidcHttpFacade = OIDCHttpFacade.class.cast(httpFacade);
    KeycloakSecurityContext securityContext = oidcHttpFacade.getSecurityContext();

    if (securityContext == null) {
        return null;
    }

    if (source.endsWith("access_token")) {
        return Arrays.asList(securityContext.getTokenString());
    }

    if (source.endsWith("id_token")) {
        return Arrays.asList(securityContext.getIdTokenString());
    }

    JsonNode jsonNode;

    if (source.startsWith("access_token[")) {
        jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getToken());
    } else if (source.startsWith("id_token[")) {
        jsonNode = JsonSerialization.mapper.valueToTree(securityContext.getIdToken());
    } else {
        throw new RuntimeException("Invalid placeholder [" + placeHolder + "]");
    }

    return JsonUtils.getValues(jsonNode, getParameter(source, "Invalid placeholder [" + placeHolder + "]"));
}