Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate old objects values to the OIDC class #41 #50

Merged
merged 13 commits into from
Dec 16, 2024
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
22 changes: 21 additions & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@
<artifactId>identity-oauth-api</artifactId>
<version>${identity.oauth.version}</version>
</dependency>
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-configuration-default</artifactId>
<version>${platform.version}</version>
</dependency>
<dependency>
<groupId>org.xwiki.commons</groupId>
<artifactId>xwiki-commons-tool-test-component</artifactId>
<version>${commons.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-bridge</artifactId>
<version>${platform.version}</version>
</dependency>
<dependency>
<groupId>org.xwiki.platform</groupId>
<artifactId>xwiki-platform-refactoring-api</artifactId>
<version>${platform.version}</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.xwiki.azureoauth.configuration;

import org.xwiki.component.annotation.Role;
import org.xwiki.stability.Unstable;

/**
* AzureAD old configuration, containing the endpoints for Identity OAuth and Azure Config.
*
* @version $Id$
* @since 2.0
*/
@Role
@Unstable
public interface AzureOldConfiguration
{
/**
* Return the tenant ID.
*
* @return the tenant ID.
*/
String getTenantID();

/**
* Return the client ID.
*
* @return the client ID.
*/
String getClientID();

/**
* Return the authentication secret.
*
* @return the authentication secret.
*/
String getSecret();

/**
* Return the provider scope.
*
* @return the provider scope.
*/
String getScope();

/**
* See if the provider configuration is active.
*
* @return {@code true} if the configuration is active, or {@code false} otherwise.
*/
boolean isActive();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.xwiki.azureoauth.configuration;

import java.util.Map;

import org.xwiki.component.annotation.Role;
import org.xwiki.configuration.ConfigurationSaveException;
import org.xwiki.stability.Unstable;

/**
* Extends {@link AzureOldConfiguration} and adds additional endpoints for the OIDC configuration.
*
* @version $Id$
* @since 2.0
*/
@Role
@Unstable
public interface EntraIDConfiguration extends AzureOldConfiguration
{
/**
* Set the configuration for the OIDC class.
*
* @param properties {@link Map} containing the properties that will be saved.
* @throws ConfigurationSaveException if any error occurs during configuration save.
*/
void setOIDCConfiguration(Map<String, Object> properties) throws ConfigurationSaveException;

/**
* Set the configuration for the Entra ID class.
*
* @param properties {@link Map} containing the properties that will be saved.
* @throws ConfigurationSaveException if any error occurs during configuration save.
*/
void setEntraIDConfiguration(Map<String, Object> properties) throws ConfigurationSaveException;

/**
* Extract the tenant ID from the OIDC Client Configuration.
*
* @return the extracted tenant ID.
*/
String getOIDCTenantID();

/**
* Check if XWiki log in is enabled for guest users.
*
* @return {@code true} if global log in is enabled and {@code false} otherwise.
*/
boolean isXWikiLoginGlobalEnabled();

/**
* Get the user groups that are allowed to log in with XWiki.
*
* @return the user groups that are allowed to log in with XWiki.
*/
String getXWikiLoginGroups();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.xwiki.azureoauth.internal;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;

import org.slf4j.Logger;
import org.xwiki.component.annotation.Component;
import org.xwiki.configuration.ConfigurationSaveException;
import org.xwiki.extension.repository.InstalledExtensionRepository;
import org.xwiki.model.reference.DocumentReferenceResolver;
import org.xwiki.query.Query;
import org.xwiki.query.QueryException;
import org.xwiki.query.QueryManager;
import org.xwiki.stability.Unstable;
import org.xwiki.wiki.descriptor.WikiDescriptorManager;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.doc.XWikiDocument;
import com.xpn.xwiki.objects.BaseObject;
import com.xwiki.azureoauth.configuration.AzureOldConfiguration;
import com.xwiki.azureoauth.configuration.EntraIDConfiguration;
import com.xwiki.azureoauth.internal.oldConfiguration.OldAzureOAuthConfiguration;

import static com.xwiki.azureoauth.internal.configuration.DefaultEntraIDConfiguration.OIDC_USER_CLASS;

/**
* Migrate old Identity OAuth configurations to the new Entra ID and OIDC configurations. Refactor the issuer value in
* the OIDC User class to be compatible to the one used by the OIDC application.
*
* @version $Id$
* @since 2.0
*/
@Component(roles = AzureADOIDCMigrator.class)
@Singleton
@Unstable
public class AzureADOIDCMigrator
oanalavinia marked this conversation as resolved.
Show resolved Hide resolved
{
private static final String INVALID_VERSION = "/2.0";

private static final String VALID_VERSION = "/v2.0";

private static final String ISSUER = "issuer";

private static final String BASE_ENDPOINT = "https://login.microsoftonline.com/%s/oauth2/v2.0/%s";

@Inject
@Named(OldAzureOAuthConfiguration.HINT)
private Provider<AzureOldConfiguration> identityOAuthConfigurationProvider;

@Inject
@Named("default")
private Provider<EntraIDConfiguration> entraIDConfigurationProvider;

@Inject
private WikiDescriptorManager wikiManager;

@Inject
private InstalledExtensionRepository installedRepository;

@Inject
private QueryManager queryManager;

@Inject
private Provider<XWikiContext> wikiContextProvider;

@Inject
@Named("current")
private DocumentReferenceResolver<String> documentReferenceResolver;

@Inject
private Logger logger;

/**
* Check if the current EntraID/OIDC configuration is empty and populate it with the old configuration from Azure
* AD.
*
* @throws ConfigurationSaveException if any error occurs while saving the new configuration.
*/
public void initializeOIDCConfiguration() throws ConfigurationSaveException
{
EntraIDConfiguration entraIDConfiguration = entraIDConfigurationProvider.get();
Map<String, Object> configurationMap = generateNewConfiguration();
if (entraIDConfiguration.getTenantID().isEmpty()) {
entraIDConfiguration.setEntraIDConfiguration(getTenantIdConfiguration());
configurationMap.putAll(getEndpoints(identityOAuthConfigurationProvider.get().getTenantID()));
logger.info("Successfully set Entra ID configuration.");
}
if (!configurationMap.isEmpty()) {
entraIDConfiguration.setOIDCConfiguration(configurationMap);
logger.info("Successfully set OIDC configuration.");
}
}

/**
* Check the OIDC issuer for already existing Azure users to make sure it has the right format and refactor it if
* needed.
*
* @throws QueryException if there is an error while executing the query to find the users.
*/
public void refactorOIDCIssuer() throws QueryException, XWikiException
{
List<String> results = this.queryManager.createQuery(
", BaseObject as obj where doc.fullName = obj.name and obj.className = :className", Query.HQL)
.setWiki(this.wikiManager.getCurrentWikiId()).bindValue("className", OIDC_USER_CLASS).execute();
XWikiContext wikiContext = wikiContextProvider.get();
XWiki wiki = wikiContext.getWiki();

for (String userRef : results) {
XWikiDocument userDoc = wiki.getDocument(documentReferenceResolver.resolve(userRef), wikiContext);
BaseObject oidcObj = userDoc.getXObject(documentReferenceResolver.resolve(OIDC_USER_CLASS));
String issuer = oidcObj.getField(ISSUER).toFormString();
if (issuer.endsWith(INVALID_VERSION)) {
int index = issuer.lastIndexOf(INVALID_VERSION);
String fixedIssuer = issuer.substring(0, index) + VALID_VERSION;
oidcObj.set(ISSUER, fixedIssuer, wikiContext);
wiki.saveDocument(userDoc, "Refactored OIDC issuer to the right format used by Entra ID.", wikiContext);
}
}
}

/**
* Generates the endpoints that are required by OIDC configuration.
*
* @param tenantID the AD tenant ID.
* @return the formatted endpoints.
*/
public Map<String, Object> getEndpoints(String tenantID)
{
return Map.of("authorizationEndpoint", String.format(BASE_ENDPOINT, tenantID, "authorize"), "tokenEndpoint",
String.format(BASE_ENDPOINT, tenantID, "token"), "logoutEndpoint",
String.format(BASE_ENDPOINT, tenantID, "logout"));
}

private Map<String, Object> generateNewConfiguration()
{
Map<String, Object> newConfig = new HashMap<>();
EntraIDConfiguration entraIDConfiguration = entraIDConfigurationProvider.get();
AzureOldConfiguration oauthConfiguration = identityOAuthConfigurationProvider.get();
if (entraIDConfiguration.getScope().isEmpty()) {
newConfig.put("scope", oauthConfiguration.getScope());
}
if (entraIDConfiguration.getClientID().isEmpty()) {
newConfig.put("clientId", oauthConfiguration.getClientID());
}
if (entraIDConfiguration.getSecret().isEmpty()) {
newConfig.put("clientSecret", oauthConfiguration.getSecret());
}
return newConfig;
}

private Map<String, Object> getTenantIdConfiguration()
{
return Map.of("tenantId", identityOAuthConfigurationProvider.get().getTenantID());
}
}
Loading