/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 net.formio;
import net.formio.data.RequestContext;
import net.formio.security.PasswordGenerator;
import net.formio.security.TokenAuthorizer;
import net.formio.security.TokenMissingException;
/**
* Operations with authorization tokens.
* @author Radek Beran
*/
final class AuthTokens {
/** Prefix of key under which the secret is stored. */
static final String SECRET_KEY_PREFIX = "formio_secret_";
static final String ALLOWED_TOKEN_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@#$%^&*";
/**
* Generates authorization token and stores it in "user related" storage (on the server)
* for later verification.
* @param ctx
* @param tokenAuthorizer
* @param rootMappingPath
* @return generated token
*/
static String generateAuthToken(RequestContext ctx, TokenAuthorizer tokenAuthorizer, String rootMappingPath) {
if (ctx == null) {
throw new IllegalStateException(RequestContext.class.getSimpleName() + " is required when the form is " +
"defined as secured. Please specify not null context in fill method.");
}
String genSecret = generateSecret();
if (ctx.getSessionStorage() == null) {
throw new IllegalStateException("User related storage must exist to store CSRF token.");
}
ctx.getSessionStorage().set(getRootMappingSecretKey(rootMappingPath), genSecret);
String reqSecret = ctx.secretWithUserIdentification(genSecret);
return tokenAuthorizer.generateToken(reqSecret);
}
/**
* Verification of authorization token. Must be called after the verification is done on nested
* mappings.
* @param ctx
* @param tokenAuthorizer
* @param rootMappingPath
* @param requestParams
* @param rootMapping true if this method is called from root mapping
* @throws InvalidTokenException if token is invalid
*/
static void verifyAuthToken(RequestContext ctx, TokenAuthorizer tokenAuthorizer, String rootMappingPath, RequestParams requestParams, boolean rootMapping, String pathSep) {
String secretKey = AuthTokens.getRootMappingSecretKey(rootMappingPath);
try {
if (ctx == null) {
throw new IllegalStateException(RequestContext.class.getSimpleName() + " is required when the form is " +
"defined as secured. Please specify not null context in bind method.");
}
String token = getAuthTokenFromRequest(requestParams, rootMappingPath, pathSep);
if ("".equals(token)) {
throw new TokenMissingException("Unauthorized attempt. Authorization token is missing! It should be posted as " + Forms.AUTH_TOKEN_FIELD_NAME +
" field. Maybe this is blocked CSRF attempt or the required field with token is not rendered in the form correctly.");
}
if (ctx.getSessionStorage() == null) {
throw new IllegalStateException("User related storage must exist to verify CSRF token.");
}
String genSecret = ctx.getSessionStorage().get(secretKey);
String reqSecret = ctx.secretWithUserIdentification(genSecret);
// InvalidTokenException is thrown for invalid token
tokenAuthorizer.validateToken(token, reqSecret);
} finally {
if (rootMapping) {
// At the end, when the whole form is submitted and data bind,
// secret for token validation held on the server side is deleted
if (ctx != null) {
ctx.getSessionStorage().delete(secretKey);
}
}
}
}
private static String getRootMappingSecretKey(String rootMappingPath) {
return SECRET_KEY_PREFIX + rootMappingPath;
}
private static String getAuthTokenFromRequest(RequestParams params, String rootMappingPath, String pathSep) {
String token = params.getParamValue(rootMappingPath + pathSep + Forms.AUTH_TOKEN_FIELD_NAME);
if (token == null) {
token = "";
}
return token;
}
private static String generateSecret() {
return PasswordGenerator.generatePassword(20, ALLOWED_TOKEN_CHARS);
}
private AuthTokens() {
throw new AssertionError("Not instantiable, use static members");
}
}