001/*
002 *  Copyright 2021 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.extrausermgt.authentication.msal;
017
018import java.net.MalformedURLException;
019import java.net.URI;
020import java.net.URISyntaxException;
021import java.net.URL;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.commons.lang3.StringUtils;
029
030import org.ametys.plugins.extrausermgt.users.entraid.EntraIDUserDirectory;
031
032import com.azure.identity.ClientSecretCredential;
033import com.azure.identity.ClientSecretCredentialBuilder;
034import com.microsoft.aad.msal4j.IAuthenticationResult;
035import com.microsoft.graph.models.User;
036import com.microsoft.graph.serviceclient.GraphServiceClient;
037
038/**
039 * Sign in through Entra ID, using the OpenId Connect protocol.
040 */
041public class EntraIDCredentialProvider extends AbstractMSALCredentialProvider implements Serviceable
042{
043    private String _tenant;
044    private String _loginAttribute;
045    
046    private OIDCScopesExtensionPoint _scopesExtensionPoint;
047    
048    public void service(ServiceManager manager) throws ServiceException
049    {
050        _scopesExtensionPoint = (OIDCScopesExtensionPoint) manager.lookup(OIDCScopesExtensionPoint.ROLE);
051    }
052    
053    @Override
054    public void init(String id, String cpModelId, Map<String, Object> paramValues, String label) throws Exception
055    {
056        super.init(id, cpModelId, paramValues, label);
057        
058        _tenant = (String) paramValues.get("authentication.entraid.tenant");
059
060        String clientID = (String) paramValues.get("authentication.entraid.appid");
061        String clientSecret = (String) paramValues.get("authentication.entraid.clientsecret");
062        boolean silent = (boolean) paramValues.get("authentication.entraid.silent");
063        boolean prompt = (boolean) paramValues.get("authentication.entraid.prompt");
064
065        init(clientID, clientSecret, prompt, silent);
066        
067        _loginAttribute = (String) paramValues.get("authentication.entraid.loginattribute");
068    }
069    
070    @Override
071    protected String getAuthority()
072    {
073        return "https://login.microsoftonline.com/" + _tenant;
074    }
075    
076    public String getIssuer()
077    {
078        return getAuthority() + "/v2.0";
079    }
080    
081    public URL getJwkSetURL()
082    {
083        try
084        {
085            return new URI(getAuthority() + "/discovery/v2.0/keys").toURL();
086        }
087        catch (MalformedURLException | URISyntaxException e)
088        {
089            throw new IllegalArgumentException("Invalid JWKSetURL", e);
090        }
091    }
092
093    @Override
094    protected Set<String> getScopes()
095    {
096        return _scopesExtensionPoint.getScopes();
097    }
098    
099    @Override
100    protected String getLogin(IAuthenticationResult result)
101    {
102        String upn = result.account().username();
103        
104        if (_loginAttribute.equals(EntraIDUserDirectory.ON_PREMISES_SAM_ACCOUNT_NAME))
105        {
106            // get the samAccountName from Entra ID
107            ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder().clientId(_clientID)
108                                                                                               .clientSecret(_clientSecret)
109                                                                                               .tenantId(_tenant)
110                                                                                               .build();
111
112            GraphServiceClient graphClient = new GraphServiceClient(clientSecretCredential);
113            
114            User user = graphClient.users().byUserId(upn).get(requestConfiguration -> {
115                requestConfiguration.queryParameters.select = new String[]{"onPremisesSamAccountName"};
116            });
117            
118            if (user != null && StringUtils.isNotBlank(user.getOnPremisesSamAccountName()))
119            {
120                return user.getOnPremisesSamAccountName();
121            }
122        }
123
124        return upn;
125    }
126}