| | |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2016 ForgeRock AS. |
| | | * Portions Copyright 2026 3A Systems, LLC. |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap.authz; |
| | | |
| | |
| | | import static org.forgerock.util.promise.Promises.newExceptionPromise; |
| | | import static org.forgerock.util.promise.Promises.newResultPromise; |
| | | |
| | | import java.io.File; |
| | | import java.io.FileInputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.nio.file.Files; |
| | | import java.nio.file.InvalidPathException; |
| | | import java.nio.file.Path; |
| | | import java.nio.file.Paths; |
| | | |
| | | import org.forgerock.http.oauth2.AccessTokenException; |
| | | import org.forgerock.http.oauth2.AccessTokenInfo; |
| | |
| | | import org.forgerock.services.context.Context; |
| | | import org.forgerock.util.promise.Promise; |
| | | |
| | | /** A file access token resolver which should only be used for test purpose.*/ |
| | | /** |
| | | * A file access token resolver which should only be used for test purpose. |
| | | * <p> |
| | | * The token is the name of a file directly inside the token folder. The token arrives verbatim from the |
| | | * {@code Authorization} header of an unauthenticated request, so a token which is not a plain file name (it |
| | | * contains a path separator, or it resolves to a file outside the folder) is refused before any file is opened. |
| | | */ |
| | | final class FileAccessTokenResolver implements AccessTokenResolver { |
| | | |
| | | private final String folderPath; |
| | | private final Path folder; |
| | | |
| | | FileAccessTokenResolver(final String folderPath) { |
| | | this.folderPath = checkNotNull(folderPath); |
| | | this.folder = Paths.get(checkNotNull(folderPath)).toAbsolutePath().normalize(); |
| | | } |
| | | |
| | | @Override |
| | | public Promise<AccessTokenInfo, AccessTokenException> resolve(final Context context, final String token) { |
| | | final Path tokenFile = tokenFile(token); |
| | | if (tokenFile == null) { |
| | | return newExceptionPromise(newAccessTokenException(ERR_OAUTH2_FILE_INVALID_TOKEN_NAME.get(token))); |
| | | } |
| | | |
| | | final JsonValue accessToken; |
| | | try (final InputStream stream = new FileInputStream(new File(folderPath, token))) { |
| | | try (final InputStream stream = Files.newInputStream(tokenFile)) { |
| | | accessToken = new JsonValue(Json.readJsonLenient(stream)); |
| | | } catch (final IOException e) { |
| | | return newExceptionPromise(newAccessTokenException(ERR_OAUTH2_FILE_NO_TOKEN.get(token) , e)); |
| | |
| | | newAccessTokenException(ERR_OAUTH2_FILE_INVALID_JSON_TOKEN.get(token, e.getMessage()), e)); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Returns the file named by the token inside the token folder, or {@code null} when the token is not a plain |
| | | * file name. Separators are refused whatever the platform treats as one, and the resolved path must still have |
| | | * the folder as its parent and the token as its name once normalized, so that {@code .}, {@code ..}, an empty |
| | | * token or an absolute path never select anything outside the folder. |
| | | */ |
| | | private Path tokenFile(final String token) { |
| | | if (token.indexOf('/') >= 0 || token.indexOf('\\') >= 0) { |
| | | return null; |
| | | } |
| | | final Path file; |
| | | try { |
| | | file = folder.resolve(token).normalize(); |
| | | } catch (final InvalidPathException e) { |
| | | return null; |
| | | } |
| | | final Path name = file.getFileName(); |
| | | return folder.equals(file.getParent()) && name != null && token.equals(name.toString()) ? file : null; |
| | | } |
| | | } |