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

com.github.susom.vertx.base.FakeAuthentication Maven / Gradle / Ivy

/*
 * Copyright 2016 The Board of Trustees of The Leland Stanford Junior University.
 *
 * 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.github.susom.vertx.base;

import io.netty.handler.codec.http.QueryStringEncoder;
import io.vertx.core.Vertx;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * This is a completely fake version of a centralized (OpenID Connect)
 * authentication server. It is to be used only for development. Instead
 * of actually performing any authentication it simply asks who you would
 * like to be.
 *
 * @author garricko
 */
public class FakeAuthentication {
  private static final Pattern USER_PATTERN = Pattern.compile("[a-zA-Z0-9_\\.\\-]{1,40}@?[a-zA-Z0-9_\\.\\-]{0,40}");
  private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z0-9 '\\.\\-]{1,40}");
//  private static final Pattern AUTHORITY_PATTERN = Pattern.compile(".*");// TODO Pattern.compile("([a-zA-Z0-9]+([:\\-][a-zA-Z0-9+])*)([,]([a-zA-Z0-9]+([:\\-][a-zA-Z0-9+])*))*");
  private final SecureRandom secureRandom;
  private final String clientId;
  private final String clientSecret;
  private final String redirectUriPrefix;
  private final Set staticAuthorities;
  private final Map codeToAuth = new HashMap<>();

  public FakeAuthentication(SecureRandom secureRandom, String clientId, String clientSecret, String redirectUriPrefix, Set staticAuthorities) {
    this.secureRandom = secureRandom;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.redirectUriPrefix = redirectUriPrefix;
    this.staticAuthorities = staticAuthorities;
  }

  public void configureRouter(Vertx vertx, Router router) {
    StrictBodyHandler smallBodyHandler = new StrictBodyHandler(4000);

    router.route().handler(new MetricsHandler(secureRandom));

    // Send a list of the static authorities we know about, so user can see them and pick from a list
    router.get("/authorities").handler(this::sendAuthorities).failureHandler(VertxBase::jsonApiFail);

    // Static login page sends us the username here
    router.post("/authenticate").handler(smallBodyHandler);
    router.post("/authenticate").handler(this::authenticate).failureHandler(VertxBase::jsonApiFail);

    // The application will use this to verify the login and obtain user info
    router.post("/token").handler(rc -> {
      // Make the HTML form encoded body accessible to getFormAttribute()
      rc.request().setExpectMultipart(true);
      rc.next();
    });
    router.post("/token").handler(smallBodyHandler);
    router.post("/token").handler(this::issueToken).failureHandler(VertxBase::jsonApiFail);

    // The application logout page will redirect here and we redirect back
    router.get("/logout").handler(this::logout).failureHandler(VertxBase::jsonApiFail);

    // Serve static html and related resources for the login/logout client
    router.get("/*").handler(new StrictResourceHandler(vertx)
        .addDir("static/fake-authentication")
        .addDir("static/assets-public", "**/*", "assets")
        .rename("login.nocache.html", "auth")
    );
  }

  private void sendAuthorities(RoutingContext rc) {
    rc.response().putHeader("content-type", "application/json").end(new JsonObject()
        .put("authorities", staticAuthorities.stream().sorted().collect(Collectors.toList())).encodePrettily() + '\n');
  }

  private void logout(RoutingContext rc) {
    String redirectUri = Valid.nonNullNormalized(rc.request().getParam("redirect_uri"), "Invalid redirect uri");
    if (!redirectUri.startsWith(redirectUriPrefix)) {
      throw new BadRequestException("Invalid redirect uri");
    }

    rc.response().setStatusCode(302).putHeader("Location", redirectUri).end();
  }

  private void authenticate(RoutingContext rc) {
    JsonObject loginJson = Valid.nonNull(rc.getBodyAsJson(), "No body");

    // Verify client id and redirect uri
    if (!Valid.safeReq(loginJson.getString("clientId"), "Invalid client").equals(clientId)) {
      throw new BadRequestException("Invalid client");
    }
    String redirectUri = Valid.nonNullNormalized(loginJson.getString("redirectUri"), "Invalid redirect uri");
    if (!redirectUri.startsWith(redirectUriPrefix)) {
      throw new BadRequestException("Invalid redirect uri");
    }
//    String redirectUri = Valid.matchesReq(loginJson.getString("redirectUri"), clientRedirectUri, "Invalid redirectUri");

    // The openid scope must be first or the only one (per standard)
    String scope = loginJson.getString("scope");
    if (scope == null || !(scope.equals("openid") || scope.startsWith("openid "))) {
      throw new BadRequestException("No scope or invalid scope");
    }

    // Generate a code and associate it with the "authenticated" user
    Auth auth = new Auth();
    auth.user = Valid.matchesReq(loginJson.getString("username"), USER_PATTERN, "Illegal username");
    auth.name = Valid.matchesOpt(loginJson.getString("displayname"), NAME_PATTERN, "Illegal displayname");
    auth.actuser = Valid.matchesOpt(loginJson.getString("actusername"), USER_PATTERN, "Illegal actusername");
    auth.actname = Valid.matchesOpt(loginJson.getString("actdisplayname"), NAME_PATTERN, "Illegal actdisplayname");
    auth.authority = new HashSet<>();
    String authorityStr = loginJson.getString("authority");//Valid.matchesOpt(loginJson.getString("authority"), AUTHORITY_PATTERN, "Illegal authority");
    if (authorityStr != null) {
      Collections.addAll(auth.authority, authorityStr.split("[,\\h\\s]"));
    }
    auth.scope = scope;
    String code = new TokenGenerator(secureRandom).create(32);
    codeToAuth.put(code, auth);

    QueryStringEncoder params = new QueryStringEncoder("");

    params.addParam("code", code);
    params.addParam("state", loginJson.getString("state"));

    rc.response().putHeader("content-type", "application/json").end(new JsonObject()
        .put("action", "redirect")
        .put("url", redirectUri + params).encodePrettily() + '\n');
  }

  private void issueToken(RoutingContext rc) {
    Valid.formAttributeEqualsShow(rc, "grant_type", "authorization_code");
    if (!Valid.safeFormAttributeReq(rc, "client_id").equals(clientId)) {
      throw new BadRequestException("Invalid client");
    }
    if (!Valid.safeFormAttributeReq(rc, "client_secret").equals(clientSecret)) {
      throw new BadRequestException("Invalid client");
    }

    String authCode = Valid.safeFormAttributeReq(rc, "code");
    Auth auth = Valid.nonNull(codeToAuth.get(authCode), "Authorization code not valid");

//      if (code == null || code.expires.isBefore(Instant.now())) {
//        throw new BadRequest("Code not valid or expired");
//      }
//
//      Valid.formAttributeEqualsHide(rc, "scope", code.scope);
//
//      Valid.formAttributeEqualsHide(rc, "redirect_uri", client.redirectUri);

    rc.response().putHeader("content-type", "application/json").end(new JsonObject()
        .put("sub", auth.user)
        .put("name", auth.name)
        .put("actsub", auth.actuser)
        .put("actname", auth.actname)
        .put("authority", new JsonArray(auth.authority.stream().collect(Collectors.toList())))
        .put("scope", auth.scope).encodePrettily() + '\n');
  }

  private static class Auth {
    String user;
    String name;
    String actuser;
    String actname;
    Set authority;
    String scope;
//    Instant expires;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy