All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.elytron.web.undertow.server.ElytronHttpExchange Maven / Gradle / Ivy

Go to download

Integration project for integrating Elytron based HTTP authentication with Undertow.

The newest version!
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2015 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.wildfly.elytron.web.undertow.server;

import static org.wildfly.common.Assert.checkNotNullParam;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;

import org.wildfly.security.auth.server.SecurityIdentity;
import org.wildfly.security.http.HttpAuthenticationException;
import org.wildfly.security.http.HttpExchangeSpi;
import org.wildfly.security.http.HttpScope;
import org.wildfly.security.http.HttpScopeNotification;
import org.wildfly.security.http.HttpServerCookie;
import org.wildfly.security.http.Scope;
import org.xnio.SslClientAuthMode;

import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.RenegotiationRequiredException;
import io.undertow.server.SSLSessionInfo;
import io.undertow.server.handlers.Cookie;
import io.undertow.server.handlers.CookieImpl;
import io.undertow.server.handlers.form.FormData;
import io.undertow.server.handlers.form.FormData.FormValue;
import io.undertow.server.handlers.form.FormDataParser;
import io.undertow.server.handlers.form.FormParserFactory;
import io.undertow.server.session.Session;
import io.undertow.server.session.SessionConfig;
import io.undertow.server.session.SessionManager;
import io.undertow.util.AbstractAttachable;
import io.undertow.util.AttachmentKey;
import io.undertow.util.Headers;
import io.undertow.util.HttpString;

/**
 * Implementation of {@link HttpExchangeSpi} to wrap access to the Undertow specific {@link HttpServerExchange}.
 *
 * @author Darran Lofthouse
 */
public class ElytronHttpExchange implements HttpExchangeSpi {

    private static final AttachmentKey HTTP_SCOPE_ATTACHMENT_KEY = AttachmentKey.create(HttpScope.class);
    private static final FormParserFactory FORM_PARSER_FACTORY = FormParserFactory.builder().build();

    private final HttpServerExchange httpServerExchange;
    private final Map> scopeResolvers;
    private final ScopeSessionListener scopeSessionListener;


    protected Map> requestParameters;

    protected ElytronHttpExchange(final HttpServerExchange httpServerExchange,
            final Map> scopeResolvers,
            final ScopeSessionListener scopeSessionListener) {
        this.httpServerExchange = checkNotNullParam("httpServerExchange", httpServerExchange);
        this.scopeResolvers = scopeResolvers;
        this.scopeSessionListener = scopeSessionListener;
    }

    protected ElytronHttpExchange(final HttpServerExchange httpServerExchange) {
        this(httpServerExchange, Collections.emptyMap(), null);
    }

    /**
     * @see org.wildfly.security.http.HttpExchangeSpi#getRequestHeaderValues(java.lang.String)
     */
    @Override
    public List getRequestHeaderValues(String headerName) {
        return httpServerExchange.getRequestHeaders().get(headerName);
    }

    /**
     * @see org.wildfly.security.http.HttpExchangeSpi#addResponseHeader(java.lang.String, java.lang.String)
     */
    @Override
    public void addResponseHeader(String headerName, String headerValue) {
        HttpString headerString = Headers.fromCache(headerName);
        httpServerExchange.getResponseHeaders().add(headerString != null ? headerString : new HttpString(headerName),
                headerValue);
    }

    /**
     * @see org.wildfly.security.http.HttpExchangeSpi#getSSLSession()
     */
    @Override
    public SSLSession getSSLSession() {
        SSLSessionInfo sessionInfo = httpServerExchange.getConnection().getSslSessionInfo();
        if (sessionInfo != null) {
            return sessionInfo.getSSLSession();
        }
        return null;
    }

    @Override
    public Certificate[] getPeerCertificates(boolean renegotiate) {
        SSLSessionInfo info = httpServerExchange.getConnection().getSslSessionInfo();
        if(info == null) {
            return null;
        }

        try {
            Certificate[] peerCertificates = info.getPeerCertificates();
            if (peerCertificates != null || renegotiate==false) return peerCertificates;
        } catch (SSLPeerUnverifiedException |RenegotiationRequiredException e) {}

        try {
            info.renegotiate(httpServerExchange, SslClientAuthMode.REQUESTED);
            return httpServerExchange.getConnection().getSslSessionInfo().getPeerCertificates();
        } catch (IOException | RenegotiationRequiredException e) {}

        return null;
    }

    /**
     * @see org.wildfly.security.http.HttpExchangeSpi#authenticationComplete(SecurityIdentity, String)
     */
    @Override
    public void authenticationComplete(SecurityIdentity securityIdentity, String mechanismName) {
        SecurityContext securityContext = httpServerExchange.getSecurityContext();
        if (securityContext != null) {
            securityContext.authenticationComplete(new ElytronAccount(securityIdentity), mechanismName, false);
        }
    }

    /**
     * @see org.wildfly.security.http.HttpExchangeSpi#authenticationFailed(String, String)
     */
    @Override
    public void authenticationFailed(String message, String mechanismName) {
        SecurityContext securityContext = httpServerExchange.getSecurityContext();
        if (securityContext != null) {
            securityContext.authenticationFailed(message, mechanismName);
        }
    }

    @Override
    public void badRequest(HttpAuthenticationException error, String mechanismName) {
    }

    @Override
    public String getRequestMethod() {
        return httpServerExchange.getRequestMethod().toString();
    }

    @Override
    public URI getRequestURI() {
        final String scheme;
        final String host;
        final int port;
        final String path;
        final String query = httpServerExchange.getQueryString();
        try {
            if (httpServerExchange.isHostIncludedInRequestURI()) {
                URI tempUri = new URI(httpServerExchange.getRequestURI());
                scheme = tempUri.getScheme();
                host = tempUri.getHost();
                port = tempUri.getPort();
                path = tempUri.getPath();
            } else {
                scheme = httpServerExchange.getRequestScheme();
                host = httpServerExchange.getHostName();
                port = httpServerExchange.getHostPort();
                path = httpServerExchange.getRequestURI();
            }
            StringBuilder uriBuilder = new StringBuilder();
            if (scheme != null) {
                uriBuilder.append(scheme);
                uriBuilder.append(':');
            }
            if (host != null) {
                uriBuilder.append("//");
                boolean needBrackets = ((host.indexOf(':') >= 0)
                        && ! host.startsWith("[")
                        && ! host.endsWith("]"));
                if (needBrackets) {
                    uriBuilder.append('[');
                }
                uriBuilder.append(host);
                if (needBrackets) {
                    uriBuilder.append(']');
                }
            }
            if (! (("http".equals(scheme) && port == 80) || ("https".equals(scheme) && port == 443))) {
                uriBuilder.append(':');
                uriBuilder.append(port);
            }
            if (path != null) {
                uriBuilder.append(path);
            }
            if (query != null && ! query.isEmpty()) {
                uriBuilder.append("?");
                uriBuilder.append(query);
            }
            return new URI(uriBuilder.toString());
        } catch (URISyntaxException e) {
            return null;
        }
    }

    @Override
    public String getRequestPath() {
        return httpServerExchange.getRelativePath();
    }

    @Override
    public Map> getRequestParameters() {
        if (requestParameters == null) {
            synchronized(this) {
                if (requestParameters == null) {
                    HashMap> parameters = new HashMap<>();

                    Map> queryParameters = httpServerExchange.getQueryParameters();

                    FormDataParser parser = FORM_PARSER_FACTORY.createParser(httpServerExchange);

                    if (parser != null) {
                        try {
                            FormData data = parser.parseBlocking();

                            for (Map.Entry> queryParametersEntry : queryParameters.entrySet()) {
                                List values = new ArrayList<>(queryParametersEntry.getValue());
                                if (data.contains(queryParametersEntry.getKey())) {
                                    Deque formValues = data.get(queryParametersEntry.getKey());
                                    formValues.stream().filter((FormValue fv) -> fv.isFile() == false)
                                            .forEach((FormValue fv) -> values.add(fv.getValue()));
                                }
                                parameters.put(queryParametersEntry.getKey(), Collections.unmodifiableList(values));
                            }

                            StreamSupport
                                    .stream(data.spliterator(), false)
                                    .filter((String s) -> parameters.containsKey(s) == false)
                                    .forEach(
                                            (String s) -> parameters.put(s,
                                                    Collections.unmodifiableList(data.get(s).stream()
                                                            .filter((FormValue v) -> v.isFile() == false)
                                                            .map((FormValue fv) -> fv.getValue()).collect(Collectors.toList()))));
                        } catch (IOException e) {}
                    } else {
                        queryParameters.forEach((name, values) -> parameters.put(name, Collections.unmodifiableList(new ArrayList(values))));
                    }
                    requestParameters = Collections.unmodifiableMap(parameters);
                }
            }
        }

        return requestParameters;
    }

    @Override
    public List getCookies() {
        Map cookies = httpServerExchange.getRequestCookies();
        return cookies.values().stream().map((Function) cookie -> new HttpServerCookie() {
            @Override
            public String getName() {
                return cookie.getName();
            }

            @Override
            public String getValue() {
                return cookie.getValue();
            }

            @Override
            public String getDomain() {
                return cookie.getDomain();
            }

            @Override
            public int getMaxAge() {
                return cookie.getMaxAge();
            }

            @Override
            public String getPath() {
                return cookie.getPath();
            }

            @Override
            public boolean isSecure() {
                return cookie.isSecure();
            }

            @Override
            public int getVersion() {
                return cookie.getVersion();
            }

            @Override
            public boolean isHttpOnly() {
                return cookie.isHttpOnly();
            }
        }).collect(Collectors.toList());
    }

    @Override
    public InputStream getRequestInputStream() {
        return httpServerExchange.getInputStream();
    }

    @Override
    public InetSocketAddress getSourceAddress() {
        return httpServerExchange.getSourceAddress();
    }

    @Override
    public void setResponseCookie(HttpServerCookie cookie) {
        CookieImpl actualCookie = new CookieImpl(cookie.getName(), cookie.getValue());

        actualCookie.setDomain(cookie.getDomain());
        actualCookie.setMaxAge(cookie.getMaxAge());
        actualCookie.setHttpOnly(cookie.isHttpOnly());
        actualCookie.setSecure(cookie.isSecure());
        actualCookie.setPath(cookie.getPath());

        httpServerExchange.setResponseCookie(actualCookie);
    }

    @Override
    public OutputStream getResponseOutputStream() {
        return httpServerExchange.getOutputStream();
    }

    @Override
    public HttpScope getScope(Scope scope) {
        if (scopeResolvers.containsKey(scope)) {
            return scopeResolvers.get(scope).apply(httpServerExchange);
        }

        switch (scope) {
            case APPLICATION: {
                SessionManager sessionManager = getSessionManager();
                if (sessionManager == null) return null;

                return new HttpScope() {
                    @Override
                    public String getID() {
                        // TODO Find a better mechanism for obtaining a unique deployment ID
                        return sessionManager.getDeploymentName();
                    }
                };
            }
            case CONNECTION:
                return getScope(httpServerExchange.getConnection());
            case EXCHANGE:
                return getScope(httpServerExchange);
            case GLOBAL:
                return null;
            case SESSION:
                return toScope(null);
            case SSL_SESSION:
                return getScope(getSSLSession());
        }
        return null;
    }

    @Override
    public Collection getScopeIds(Scope scope) {
        if (scope == Scope.SESSION) {
            SessionManager sessionManager = getSessionManager();
            return sessionManager.getAllSessions();
        }

        return null;
    }

    @Override
    public HttpScope getScope(Scope scope, String id) {
        if (scope == Scope.SESSION) {
            return toScope(id);
        }
        return null;
    }

    @Override
    public void setStatusCode(int statusCode) {
        if (httpServerExchange.isResponseStarted() == false) {
            httpServerExchange.setStatusCode(statusCode);
        }
    }

    @Override
    public String getRemoteUser() {
        return httpServerExchange.getAttachment(HttpServerExchange.REMOTE_USER);
    }

    /**
     * Sub-types may override this method to define how {@link SessionManager} is obtained.
     *
     * @return the {@link SessionManager}
     */
    protected  SessionManager getSessionManager() {
        return httpServerExchange.getAttachment(SessionManager.ATTACHMENT_KEY);
    }

    /**
     * Sub-types may override this method to define how {@link SessionConfig} is obtained.
     *
     * @return the {@link SessionConfig}
     */
    protected SessionConfig getSessionConfig() {
        return httpServerExchange.getAttachment(SessionConfig.ATTACHMENT_KEY);
    }

    private HttpScope toScope(String id) {
        SessionManager sessionManager = getSessionManager();
        SessionConfig sessionConfig = getSessionConfig();

        if (sessionManager == null || sessionConfig == null) {
            return null;
        }

        return new HttpScope() {

            private Session session = id == null ? sessionManager.getSession(httpServerExchange, sessionConfig) : sessionManager.getSession(id);

            @Override
            public String getID() {
                if (exists()) {
                    return session.getId();
                }
                return null;
            }

            @Override
            public boolean exists() {
                return session != null;
            }

            @Override
            public boolean create() {
                if (exists()) {
                    return false;
                }
                session = sessionManager.createSession(httpServerExchange, sessionConfig);
                return session != null;
            }

            @Override
            public boolean supportsAttachments() {
                return exists();
            }

            @Override
            public void setAttachment(String key, Object value) {
                if (supportsAttachments()) {
                    session.setAttribute(key, value);
                }
            }

            @Override
            public Object getAttachment(String key) {
                if (supportsAttachments()) {
                    return session.getAttribute(key);
                }
                return null;
            }

            @Override
            public boolean supportsInvalidation() {
                return exists();
            }

            @Override
            public boolean invalidate() {
                if (supportsInvalidation()) {
                    session.invalidate(httpServerExchange);
                    return true;
                }
                return false;
            }

            @Override
            public boolean supportsNotifications() {
                return exists() && scopeSessionListener != null;
            }

            @Override
            public void registerForNotification(Consumer notificationConsumer) {
                if (supportsNotifications()) {
                    scopeSessionListener.registerListener(session.getId(), notificationConsumer);
                }
            }
        };
    }

    private HttpScope getScope(AbstractAttachable attachable) {
        HttpScope httpScope = attachable.getAttachment(HTTP_SCOPE_ATTACHMENT_KEY);
        if (httpScope == null) {
            synchronized (attachable) {
                httpScope = attachable.getAttachment(HTTP_SCOPE_ATTACHMENT_KEY);
                if (httpScope == null) {
                    final Map storageMap = new HashMap<>();
                    httpScope = new HttpScope() {

                        @Override
                        public boolean exists() {
                            return true;
                        }

                        @Override
                        public boolean create() {
                            return false;
                        }

                        @Override
                        public boolean supportsAttachments() {
                            return true;
                        }

                        @Override
                        public void setAttachment(String key, Object value) {
                            if (value != null) {
                                storageMap.put(key, value);
                            } else {
                                storageMap.remove(key);
                            }
                        }

                        @Override
                        public Object getAttachment(String key) {
                            return storageMap.get(key);
                        }

                    };

                    attachable.putAttachment(HTTP_SCOPE_ATTACHMENT_KEY, httpScope);
                }
            }
        }

        return httpScope;
    }

    private HttpScope getScope(final SSLSession sslSession) {
        if (sslSession == null) {
            return null;
        }

        return new HttpScope() {

            @Override
            public boolean exists() {
                return true;
            }

            @Override
            public boolean create() {
                return false;
            }

            @Override
            public boolean supportsAttachments() {
                return true;
            }

            @Override
            public void setAttachment(String key, Object value) {
                sslSession.putValue(key, value);
            }

            @Override
            public Object getAttachment(String key) {
                return sslSession.getValue(key);
            }

        };


    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy