Skip to content
This repository was archived by the owner on Jan 20, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 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 com.networknt.oauth.security;

import com.hazelcast.core.IMap;
import com.networknt.oauth.cache.CacheStartupHookProvider;
import com.networknt.oauth.cache.model.Client;
import io.undertow.UndertowLogger;
import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.SecurityContext;
import io.undertow.security.idm.Account;
import io.undertow.security.idm.IdentityManager;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.form.FormData;
import io.undertow.server.handlers.form.FormDataParser;
import io.undertow.server.handlers.form.FormParserFactory;
import io.undertow.server.session.Session;
import io.undertow.util.*;

import java.io.IOException;

import static io.undertow.UndertowMessages.MESSAGES;

/**
* A customized FormAuthenticationMechanism that allows authenticator and authorizer injection.
*
* @author Steve Hu
*/
public class LightFormAuthenticationMechanism implements AuthenticationMechanism {

public static final String LOCATION_ATTRIBUTE = LightFormAuthenticationMechanism.class.getName() + ".LOCATION";

public static final String DEFAULT_POST_LOCATION = "/j_security_check";

private final String name;
private final String loginPage;
private final String errorPage;
private final String postLocation;
private final FormParserFactory formParserFactory;
private final IdentityManager identityManager;

public LightFormAuthenticationMechanism(final String name, final String loginPage, final String errorPage) {
this(FormParserFactory.builder().build(), name, loginPage, errorPage);
}

public LightFormAuthenticationMechanism(final String name, final String loginPage, final String errorPage, final String postLocation) {
this(FormParserFactory.builder().build(), name, loginPage, errorPage, postLocation);
}

public LightFormAuthenticationMechanism(final FormParserFactory formParserFactory, final String name, final String loginPage, final String errorPage) {
this(formParserFactory, name, loginPage, errorPage, DEFAULT_POST_LOCATION);
}

public LightFormAuthenticationMechanism(final FormParserFactory formParserFactory, final String name, final String loginPage, final String errorPage, final IdentityManager identityManager) {
this(formParserFactory, name, loginPage, errorPage, DEFAULT_POST_LOCATION, identityManager);
}

public LightFormAuthenticationMechanism(final FormParserFactory formParserFactory, final String name, final String loginPage, final String errorPage, final String postLocation) {
this(formParserFactory, name, loginPage, errorPage, postLocation, null);
}

public LightFormAuthenticationMechanism(final FormParserFactory formParserFactory, final String name, final String loginPage, final String errorPage, final String postLocation, final IdentityManager identityManager) {
this.name = name;
this.loginPage = loginPage;
this.errorPage = errorPage;
this.postLocation = postLocation;
this.formParserFactory = formParserFactory;
this.identityManager = identityManager;
}

@SuppressWarnings("deprecation")
private IdentityManager getIdentityManager(SecurityContext securityContext) {
return identityManager != null ? identityManager : securityContext.getIdentityManager();
}

@Override
public AuthenticationMechanismOutcome authenticate(final HttpServerExchange exchange,
final SecurityContext securityContext) {
if (exchange.getRequestPath().endsWith(postLocation) && exchange.getRequestMethod().equals(Methods.POST)) {
return runFormAuth(exchange, securityContext);
} else {
return AuthenticationMechanismOutcome.NOT_ATTEMPTED;
}
}

public AuthenticationMechanismOutcome runFormAuth(final HttpServerExchange exchange, final SecurityContext securityContext) {
final FormDataParser parser = formParserFactory.createParser(exchange);
if (parser == null) {
UndertowLogger.SECURITY_LOGGER.debug("Could not authenticate as no form parser is present");
// TODO - May need a better error signaling mechanism here to prevent repeated attempts.
return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
}

try {
final FormData data = parser.parseBlocking();
final FormData.FormValue jUsername = data.getFirst("j_username");
final FormData.FormValue jPassword = data.getFirst("j_password");
final FormData.FormValue jClientId = data.getFirst("client_id");
final FormData.FormValue jUserType = data.getFirst("user_type");
if (jUsername == null || jPassword == null) {
UndertowLogger.SECURITY_LOGGER.debugf("Could not authenticate as username or password was not present in the posted result for %s", exchange);
return AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
}
final String userName = jUsername.getValue();
final String password = jPassword.getValue();
final String userType = jUserType.getValue();
final String clientId = jClientId.getValue();

// get clientAuthClass and userType
String clientAuthClass = null;
IMap<String, Client> clients = CacheStartupHookProvider.hz.getMap("clients");
Client client = clients.get(clientId);
if(client != null) {
clientAuthClass = client.getAuthenticateClass();
}

AuthenticationMechanismOutcome outcome = null;
LightPasswordCredential credential = new LightPasswordCredential(password.toCharArray(), clientAuthClass, userType);
try {
IdentityManager identityManager = getIdentityManager(securityContext);
Account account = identityManager.verify(userName, credential);
if (account != null) {
securityContext.authenticationComplete(account, name, true);
UndertowLogger.SECURITY_LOGGER.debugf("Authenticated user %s using for auth for %s", account.getPrincipal().getName(), exchange);
outcome = AuthenticationMechanismOutcome.AUTHENTICATED;
} else {
securityContext.authenticationFailed(MESSAGES.authenticationFailed(userName), name);
}
} finally {
// if (outcome == AuthenticationMechanismOutcome.AUTHENTICATED) {
// handleRedirectBack(exchange);
// exchange.endExchange();
// }
return outcome != null ? outcome : AuthenticationMechanismOutcome.NOT_AUTHENTICATED;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}

/*
protected void handleRedirectBack(final HttpServerExchange exchange) {
final Session session = Sessions.getSession(exchange);
if (session != null) {
final String location = (String) session.removeAttribute(LOCATION_ATTRIBUTE);
if(location != null) {
exchange.addDefaultResponseListener(new DefaultResponseListener() {
@Override
public boolean handleDefaultResponse(final HttpServerExchange exchange) {
exchange.getResponseHeaders().put(Headers.LOCATION, location);
exchange.setStatusCode(StatusCodes.FOUND);
exchange.endExchange();
return true;
}
});
}

}
}
*/
public ChallengeResult sendChallenge(final HttpServerExchange exchange, final SecurityContext securityContext) {
if (exchange.getRequestPath().endsWith(postLocation) && exchange.getRequestMethod().equals(Methods.POST)) {
UndertowLogger.SECURITY_LOGGER.debugf("Serving form auth error page %s for %s", loginPage, exchange);
// This method would no longer be called if authentication had already occurred.
Integer code = servePage(exchange, errorPage);
return new ChallengeResult(true, code);
} else {
UndertowLogger.SECURITY_LOGGER.debugf("Serving login form %s for %s", loginPage, exchange);

// we need to store the URL
storeInitialLocation(exchange);
// TODO - Rather than redirecting, in order to make this mechanism compatible with the other mechanisms we need to
// return the actual error page not a redirect.
Integer code = servePage(exchange, loginPage);
return new ChallengeResult(true, code);
}
}

protected void storeInitialLocation(final HttpServerExchange exchange) {
Session session = Sessions.getOrCreateSession(exchange);
session.setAttribute(LOCATION_ATTRIBUTE, RedirectBuilder.redirect(exchange, exchange.getRelativePath()));
}

protected Integer servePage(final HttpServerExchange exchange, final String location) {
sendRedirect(exchange, location);
return StatusCodes.TEMPORARY_REDIRECT;
}


static void sendRedirect(final HttpServerExchange exchange, final String location) {
// TODO - String concatenation to construct URLS is extremely error prone - switch to a URI which will better handle this.
String loc = exchange.getRequestScheme() + "://" + exchange.getHostAndPort() + location;
exchange.getResponseHeaders().put(Headers.LOCATION, loc);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import io.undertow.security.idm.Account;
import io.undertow.security.idm.Credential;
import io.undertow.security.idm.IdentityManager;
import io.undertow.security.idm.PasswordCredential;
import org.ietf.jgss.GSSException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.networknt.config.Config;
import com.networknt.oauth.security.LightBasicAuthenticationMechanism;
import com.networknt.oauth.security.LightFormAuthenticationMechanism;
import com.networknt.oauth.security.LightGSSAPIAuthenticationMechanism;
import com.networknt.oauth.security.LightIdentityManager;
import io.undertow.security.api.AuthenticationMechanism;
Expand Down Expand Up @@ -67,7 +68,7 @@ protected HttpHandler addFormSecurity(final HttpHandler toWrap, final IdentityMa
handler = new AuthenticationConstraintHandler(handler);
final List<AuthenticationMechanism> mechanisms = new ArrayList<>();
mechanisms.add(new CachedAuthenticatedSessionMechanism());
mechanisms.add(new FormAuthenticationMechanism("oauth2", "/login", "/error", "/oauth2/code"));
mechanisms.add(new LightFormAuthenticationMechanism("oauth2", "/login", "/error", "/oauth2/code"));
handler = new AuthenticationMechanismsHandler(handler, mechanisms);
handler = new SecurityInitialHandler(AuthenticationMode.PRO_ACTIVE, identityManager, handler);
handler = new SessionAttachmentHandler(handler, new InMemorySessionManager("oauth2"), new SessionCookieConfig());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
import com.networknt.oauth.cache.model.Client;
import com.networknt.status.Status;
import com.networknt.utility.Util;
import io.undertow.UndertowLogger;
import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpHandler;
import io.undertow.server.HttpServerExchange;
import io.undertow.server.handlers.form.FormData;
import io.undertow.server.handlers.form.FormDataParser;
import io.undertow.util.Headers;
import io.undertow.util.StatusCodes;
import org.slf4j.Logger;
Expand All @@ -26,37 +30,23 @@ public class Oauth2CodePostHandler extends CodeAuditHandler implements LightHttp
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "application/json");

// parse all the parameters here as this is a redirected get request.
Map<String, String> params = new HashMap<>();
Map<String, Deque<String>> pnames = exchange.getQueryParameters();
for (Map.Entry<String, Deque<String>> entry : pnames.entrySet()) {
String pname = entry.getKey();
Iterator<String> pvalues = entry.getValue().iterator();
if(pvalues.hasNext()) {
params.put(pname, pvalues.next());
}
}
if(logger.isDebugEnabled()) logger.debug("params", params);
String clientId = params.get("client_id");
// get the form from the exchange
final FormData data = exchange.getAttachment(FormDataParser.FORM_DATA);

final FormData.FormValue jClientId = data.getFirst("client_id");
final FormData.FormValue jRedirectUri = data.getFirst("redirect_uri");
final FormData.FormValue jState = data.getFirst("state");
final String clientId = jClientId.getValue();
String redirectUri = jRedirectUri == null ? null : jRedirectUri.getValue();
final String state = jState == null ? null : jState.getValue();

// check if the client_id is valid
IMap<String, Client> clients = CacheStartupHookProvider.hz.getMap("clients");
Client client = clients.get(clientId);
if(client == null) {
setExchangeStatus(exchange, CLIENT_NOT_FOUND, clientId);
processAudit(exchange);
} else {
/*
String clazz = (String)client.get("authenticateClass");
if(clazz == null) clazz = DEFAULT_AUTHENTICATE_CLASS;
Authentication auth = (Authentication)Class.forName(clazz).getConstructor().newInstance();
String userId = auth.authenticate(exchange);
if(userId == null) {
Status status = new Status(MISSING_AUTHORIZATION_HEADER, clientId);
exchange.setStatusCode(status.getStatusCode());
exchange.getResponseSender().send(status.toString());
return;
}
*/
final SecurityContext context = exchange.getSecurityContext();
String userId = context.getAuthenticatedAccount().getPrincipal().getName();
Set<String> roles = context.getAuthenticatedAccount().getRoles();
Expand All @@ -67,7 +57,6 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
}
// generate auth code
String code = Util.getUUID();
String redirectUri = params.get("redirect_uri");
if(redirectUri == null) {
redirectUri = client.getRedirectUri();
} else {
Expand All @@ -76,7 +65,6 @@ public void handleRequest(HttpServerExchange exchange) throws Exception {
CacheStartupHookProvider.hz.getMap("codes").set(code, codeMap);

redirectUri = redirectUri + "?code=" + code;
String state = params.get("state");
if(state != null) {
redirectUri = redirectUri + "&state=" + state;
}
Expand Down
Loading