001/* 002 * Copyright 2016 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.core.impl.authentication; 017 018import java.util.Collections; 019import java.util.List; 020import java.util.Map; 021import java.util.StringTokenizer; 022 023import org.apache.avalon.framework.context.Context; 024import org.apache.avalon.framework.context.ContextException; 025import org.apache.avalon.framework.context.Contextualizable; 026import org.apache.cocoon.components.ContextHelper; 027import org.apache.cocoon.environment.Redirector; 028import org.apache.cocoon.environment.Request; 029import org.apache.commons.codec.binary.Base64; 030import org.apache.commons.lang3.StringUtils; 031 032import org.ametys.core.authentication.AbstractCredentialProvider; 033import org.ametys.core.authentication.AuthenticateAction; 034import org.ametys.core.authentication.BlockingCredentialProvider; 035import org.ametys.core.user.UserIdentity; 036import org.ametys.core.user.directory.UserDirectory; 037import org.ametys.core.user.directory.UserDirectory.CredentialsResult; 038import org.ametys.core.user.population.UserPopulation; 039import org.ametys.runtime.authentication.AuthorizationRequiredException; 040 041/** 042 * Basic http authentication. 043 */ 044public class BasicCredentialProvider extends AbstractCredentialProvider implements BlockingCredentialProvider, Contextualizable 045{ 046 /** Name of the parameter holding the authentication realm */ 047 protected static final String __PARAM_REALM = "runtime.authentication.basic.realm"; 048 049 private static final String BASIC_AUTHENTICATION_KEY = "BASIC "; 050 051 /** The realm */ 052 protected String _realm; 053 054 private Context _context; 055 056 @Override 057 public void contextualize(Context context) throws ContextException 058 { 059 _context = context; 060 } 061 062 @Override 063 public void init(String id, String cpModelId, Map<String, Object> paramValues, String label) throws Exception 064 { 065 super.init(id, cpModelId, paramValues, label); 066 _realm = (String) paramValues.get(__PARAM_REALM); 067 } 068 069 @Override 070 public boolean blockingIsStillConnected(UserIdentity userIdentity, Redirector redirector) throws Exception 071 { 072 // this manager is always valid 073 return true; 074 } 075 076 @Override 077 public boolean blockingGrantAnonymousRequest() 078 { 079 // this implementation does not have any particular request 080 // to take into account 081 return false; 082 } 083 084 @Override 085 public UserIdentity blockingGetUserIdentity(Redirector redirector) throws Exception 086 { 087 Request request = ContextHelper.getRequest(_context); 088 089 // Check authentication header 090 String auth = request.getHeader("Authorization"); 091 if (auth == null) 092 { 093 // no auth 094 throw new AuthorizationRequiredException(_realm); 095 } 096 097 if (!auth.toUpperCase().startsWith(BASIC_AUTHENTICATION_KEY)) 098 { 099 // we only do BASIC 100 return null; 101 } 102 103 // Get encoded user and password, comes after "BASIC " 104 String userpassEncoded = auth.substring(BASIC_AUTHENTICATION_KEY.length()); 105 106 // Decode it, using any base 64 decoder 107 String userpassDecoded = new String(Base64.decodeBase64(userpassEncoded.getBytes("UTF-8")), "UTF-8"); 108 109 // Login and password are separated with a : 110 StringTokenizer stk = new StringTokenizer(userpassDecoded, ":"); 111 String login = stk.hasMoreTokens() ? stk.nextToken() : ""; 112 String password = stk.hasMoreTokens() ? stk.nextToken() : ""; 113 114 // Let's check password 115 List<UserPopulation> userPopulations = _getPopulations(request); 116 for (UserPopulation userPopulation : userPopulations) 117 { 118 for (UserDirectory userDirectory : userPopulation.getUserDirectories()) 119 { 120 if (userDirectory.getUser(login) != null) 121 { 122 if (userDirectory.checkCredentials(login, password) != CredentialsResult.NOT_AUTHENTICATED) 123 { 124 return new UserIdentity(login, userPopulation.getId()); 125 } 126 else 127 { 128 throw new AuthorizationRequiredException(_realm); 129 } 130 } 131 } 132 } 133 134 throw new AuthorizationRequiredException(_realm); 135 } 136 137 private List<UserPopulation> _getPopulations(Request request) 138 { 139 // With forms, the population should always be known! 140 @SuppressWarnings("unchecked") 141 List<UserPopulation> userPopulations = (List<UserPopulation>) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_AVAILABLE_USER_POPULATIONS_LIST); 142 143 // In this list a population was maybe chosen? 144 final String chosenUserPopulationId = (String) request.getAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_USER_POPULATION_ID); 145 if (StringUtils.isNotBlank(chosenUserPopulationId)) 146 { 147 UserPopulation chosenUserPopulation = userPopulations.stream().filter(userPopulation -> StringUtils.equals(userPopulation.getId(), chosenUserPopulationId)).findFirst().get(); 148 return Collections.singletonList(chosenUserPopulation); 149 } 150 151 // If the list has one element only... 152 return userPopulations; 153 } 154 155 @Override 156 public void blockingUserNotAllowed(Redirector redirector) 157 { 158 // empty method, nothing more to do 159 } 160 161 @Override 162 public void blockingUserAllowed(UserIdentity userIdentity, Redirector redirector) 163 { 164 // empty method, nothing more to do 165 } 166 167 @Override 168 public boolean requiresNewWindow() 169 { 170 return false; 171 } 172}