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

io.vertigo.account.plugins.identityprovider.text.TextIdentityProviderPlugin Maven / Gradle / Ivy

The newest version!
/*
 * vertigo - application development platform
 *
 * Copyright (C) 2013-2024, Vertigo.io, [email protected]
 *
 * 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 io.vertigo.account.plugins.identityprovider.text;

import java.io.Serializable;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;

import io.vertigo.account.impl.identityprovider.IdentityProviderPlugin;
import io.vertigo.core.lang.Assertion;
import io.vertigo.core.lang.WrappedException;
import io.vertigo.core.node.Node;
import io.vertigo.core.node.component.Activeable;
import io.vertigo.core.param.ParamValue;
import io.vertigo.core.resource.ResourceManager;
import io.vertigo.core.util.FileUtil;
import io.vertigo.datamodel.data.definitions.DataDefinition;
import io.vertigo.datamodel.data.definitions.DataField;
import io.vertigo.datamodel.data.model.Entity;
import io.vertigo.datamodel.data.model.UID;
import io.vertigo.datamodel.data.util.DataModelUtil;
import io.vertigo.datamodel.smarttype.SmartTypeManager;
import io.vertigo.datamodel.smarttype.definitions.FormatterException;
import io.vertigo.datastore.filestore.model.VFile;
import io.vertigo.datastore.impl.filestore.model.FSFile;

/**
 * A simple implementation of the Realm interface that
 * uses a set of configured user accounts and roles to support authentication and authorization.  Each account entry
 * specifies the username, password, and roles for a user.  Roles can also be mapped
 * to permissions and associated with users.
 * 

* User accounts and roles are stored in two {@code Map}s in memory, so it is expected that the total number of either * is not sufficiently large. * * @since 0.1 */ public class TextIdentityProviderPlugin implements IdentityProviderPlugin, Activeable { private static final String PHOTO_URL_RESERVED_FIELD = "photoUrl"; private static final Pattern PATTERN_FIELD_PARSER = Pattern.compile("\\(?<([^>]+)>[^)]*\\)"); private final Pattern filePattern; private final List filePatternFieldsOrdered; private final Map authToUsers; //auth-to-Account private final Map idsToUsers; //id-to-Account private final ResourceManager resourceManager; private final SmartTypeManager smartTypeManager; private final String filePath; private final String userIdentityEntity; private final String userAuthField; /** * Constructor. * @param resourceManager Resource Manager * @param filePath File path * @param filePatternStr File Pattern (id, displayName, email, authToken, photoUrl) * @param userAuthField Authent token field name * @param userIdentityEntity User dataDefinition name */ @Inject public TextIdentityProviderPlugin( @ParamValue("identityFilePath") final String filePath, @ParamValue("identityFilePattern") final String filePatternStr, @ParamValue("userAuthField") final String userAuthField, @ParamValue("userIdentityEntity") final String userIdentityEntity, final ResourceManager resourceManager, final SmartTypeManager smartTypeManager) { Assertion.check() .isNotNull(resourceManager) .isNotNull(smartTypeManager) .isNotBlank(filePatternStr) .isTrue(filePatternStr.contains("(?<"), "filePattern should be a regexp of named group for each User's entity fields plus reserved field '{0}' " + "(like : '(?\\S+);(?\\S+);(?\\S+);;(?<{0}>\\S+)' )", PHOTO_URL_RESERVED_FIELD) .isTrue(filePatternStr.contains("(?<" + PHOTO_URL_RESERVED_FIELD + ">"), "filePattern should be a regexp of named group for each User's entity fields plus reserved field '{0}' " + "(like : '(?\\S+);(?\\S+);(?\\S+);;(?<{0}>\\S+)' )", PHOTO_URL_RESERVED_FIELD) .isTrue(filePatternStr.contains("(?<" + userAuthField + ">"), "filePattern should contains the userAuthField : {0}", userAuthField) .isNotBlank(userIdentityEntity) .isNotBlank(userAuthField); // ----- this.resourceManager = resourceManager; this.smartTypeManager = smartTypeManager; this.filePath = filePath; //SimpleAccountRealms are memory-only realms authToUsers = new LinkedHashMap<>(); idsToUsers = new LinkedHashMap<>(); filePattern = Pattern.compile(filePatternStr); filePatternFieldsOrdered = parsePatternFields(filePatternStr); this.userIdentityEntity = userIdentityEntity; this.userAuthField = userAuthField; } private static List parsePatternFields(final String filePatternStr) { final List fields = new ArrayList<>(); final Matcher matcher = PATTERN_FIELD_PARSER.matcher(filePatternStr); while (matcher.find()) { fields.add(matcher.group(1)); } return fields; } /** {@inheritDoc} */ @Override public long getUsersCount() { return authToUsers.size(); } /** {@inheritDoc} */ @Override public List getAllUsers() { return (List) authToUsers.values().stream() .map(IdentityUserInfo::getUser) .toList(); } /** {@inheritDoc} */ @Override public E getUserByAuthToken(final String userAuthToken) { return (E) authToUsers.get(userAuthToken).getUser(); } /** {@inheritDoc} */ @Override public Optional getPhoto(final UID accountURI) { final IdentityUserInfo identityAccountInfo = idsToUsers.get(accountURI.getId()); Assertion.check().isNotNull(identityAccountInfo, "No identity found for {0}", accountURI); final String photoUrl = identityAccountInfo.getPhotoUrl(); if (photoUrl == null || photoUrl.isEmpty()) { return Optional.empty(); } final URL fileURL; if (photoUrl.startsWith(".")) {//si on est en relatif, on repart du prefix du fichier des identities final String accountFilePrefix = filePath.substring(0, filePath.lastIndexOf('/')) + "/"; fileURL = resourceManager.resolve(accountFilePrefix + photoUrl); } else { fileURL = resourceManager.resolve(photoUrl); } return createVFile(accountURI, fileURL, photoUrl); } private static Optional createVFile(final UID accountURI, final URL fileURL, final String photoUrl) { Path photoFile; try { photoFile = Paths.get(fileURL.toURI()); } catch (final URISyntaxException e) { return Optional.empty(); } Assertion.check() .isTrue(photoFile.toFile().exists(), "Identity {0} photo {1} not found", accountURI, photoUrl) .isTrue(photoFile.toFile().isFile(), "Identity {0} photo {1} must be a file", accountURI, photoUrl); return Optional.of(FSFile.of(photoFile)); } /** {@inheritDoc} */ @Override public void start() { final DataDefinition userDtDefinition = Node.getNode().getDefinitionSpace().resolve(userIdentityEntity, DataDefinition.class); Assertion.check().isTrue(userDtDefinition.contains(userAuthField), "User definition ({0}) should contains the userAuthField ({1})", userIdentityEntity, userAuthField); final URL realmURL = resourceManager.resolve(filePath); final String confTest = FileUtil.read(realmURL); try (final Scanner scanner = new Scanner(confTest)) { while (scanner.hasNextLine()) { final String line = scanner.nextLine(); parseAndPopulateUserInfo(line, userDtDefinition); } } } private void parseAndPopulateUserInfo(final String line, final DataDefinition userDtDefinition) { final Matcher matcher = filePattern.matcher(line); if (!matcher.find()) { throw new IllegalArgumentException("Can't parse textIdentity file , pattern can't match"); } String photoUrl = null; String userAuthToken = null; final Entity user = Entity.class.cast(DataModelUtil.createDataObject(userDtDefinition)); for (final String fieldName : filePatternFieldsOrdered) { final String valueStr = matcher.group(fieldName); if (PHOTO_URL_RESERVED_FIELD.equals(fieldName)) { photoUrl = valueStr; } else { setTypedValue(userDtDefinition, user, fieldName, valueStr); if (userAuthField.equals(fieldName)) { userAuthToken = valueStr; } } } Assertion.check().isNotBlank(userAuthToken, "User AuthToken not found in file"); final IdentityUserInfo userInfo = new IdentityUserInfo(user, photoUrl); authToUsers.put(userAuthToken, userInfo); idsToUsers.put(user.getUID().getId(), userInfo); } private void setTypedValue(final DataDefinition userDtDefinition, final Entity user, final String fieldName, final String valueStr) { final DataField dtField = userDtDefinition.getField(fieldName); final Serializable typedValue; try { typedValue = (Serializable) smartTypeManager.stringToValue(dtField.smartTypeDefinition(), valueStr); } catch (final FormatterException e) { throw WrappedException.wrap(e); } dtField.getDataAccessor().setValue(user, typedValue); } /** {@inheritDoc} */ @Override public void stop() { authToUsers.clear(); idsToUsers.clear(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy