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