001package org.apache.archiva.redback.common.ldap.connection; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.commons.lang3.StringUtils; 023import org.slf4j.Logger; 024import org.slf4j.LoggerFactory; 025 026import javax.naming.Context; 027import javax.naming.NamingException; 028import javax.naming.directory.DirContext; 029import javax.naming.ldap.LdapName; 030import javax.naming.ldap.Rdn; 031import java.io.IOException; 032import java.util.Collections; 033import java.util.Hashtable; 034import java.util.List; 035import java.util.Properties; 036import java.util.concurrent.atomic.AtomicBoolean; 037import javax.naming.spi.NamingManager; 038 039/** 040 * The configuration for a connection will not change. 041 * 042 * @author <a href="mailto:trygvis@inamo.no">trygvis</a> 043 * @since 2.1 044 */ 045public class DefaultLdapConnection 046 implements LdapConnection 047{ 048 049 private Logger log = LoggerFactory.getLogger( getClass() ); 050 051 private LdapConnectionConfiguration config; 052 053 private DirContext context; 054 055 private List<Rdn> baseDnRdns; 056 057 private AtomicBoolean open = new AtomicBoolean( false ); 058 059 public DefaultLdapConnection( LdapConnectionConfiguration config, Rdn subRdn ) 060 throws LdapException 061 { 062 this.config = config; 063 064 if( config.getBaseDn() == null ) { 065 throw new LdapException( "Invalid BaseDn in the configuration." ); 066 } 067 068 LdapName baseDn = new LdapName( config.getBaseDn().getRdns() ); 069 070 if ( subRdn != null ) 071 { 072 baseDn.add( subRdn ); 073 } 074 075 log.debug( "baseDn: {}", baseDn ); 076 077 baseDnRdns = Collections.unmodifiableList( baseDn.getRdns() ); 078 079 if ( context != null ) 080 { 081 throw new LdapException( "Already connected." ); 082 } 083 084 log.debug( "baseDnRdns: {}", baseDnRdns ); 085 086 Hashtable<Object, Object> e = getEnvironment(); 087 088 try 089 { 090 context = (DirContext) NamingManager.getInitialContext( e ); 091 this.open.set( true ); 092 } 093 catch ( NamingException ex ) 094 { 095 throw new LdapException( "Could not connect to the server.", ex ); 096 } 097 } 098 099 /** 100 * This ldap connection will attempt to establish a connection using the configuration, 101 * replacing the principal and the password 102 * 103 * @param config 104 * @param bindDn 105 * @param password 106 * @throws LdapException 107 */ 108 public DefaultLdapConnection( LdapConnectionConfiguration config, String bindDn, String password ) 109 throws LdapException 110 { 111 this.config = config; 112 113 Hashtable<Object, Object> e = getEnvironment(); 114 115 e.put( Context.SECURITY_PRINCIPAL, bindDn ); 116 e.put( Context.SECURITY_CREDENTIALS, password ); 117 118 try 119 { 120 context = (DirContext) NamingManager.getInitialContext( e ); 121 this.open.set( true ); 122 } 123 catch ( NamingException ex ) 124 { 125 throw new LdapException( "Could not connect to the server.", ex ); 126 } 127 } 128 129 // ---------------------------------------------------------------------- 130 // Connection Managment 131 // ---------------------------------------------------------------------- 132 133 @Override 134 public Hashtable<Object, Object> getEnvironment() 135 throws LdapException 136 { 137 Properties env = new Properties(); 138 139 env.putAll( config.getExtraProperties() ); 140 141 config.check(); 142 143 env.put( Context.INITIAL_CONTEXT_FACTORY, config.getContextFactory() ); 144 145 // REDBACK-289/MRM-1488 146 // enable connection pooling when using Sun's LDAP context factory 147 if ( config.getContextFactory().equals( "com.sun.jndi.ldap.LdapCtxFactory" ) ) 148 { 149 env.put( "com.sun.jndi.ldap.connect.pool", "true" ); 150 151 env.put( "com.sun.jndi.ldap.connect.pool.timeout", "3600" ); 152 } 153 154 if ( config.getHostname() != null ) 155 { 156 String protocol = "ldap";// config.isSsl() ? "ldaps" : "ldap"; 157 if ( config.getPort() != 0 ) 158 { 159 env.put( Context.PROVIDER_URL, protocol + "://" + config.getHostname() + ":" + config.getPort() + "/" ); 160 } 161 else 162 { 163 env.put( Context.PROVIDER_URL, protocol + "://" + config.getHostname() + "/" ); 164 } 165 } 166 167 if ( config.isSsl() ) 168 { 169 env.put( Context.SECURITY_PROTOCOL, "ssl" ); 170 } 171 172 if ( config.getAuthenticationMethod() != null ) 173 { 174 env.put( Context.SECURITY_AUTHENTICATION, config.getAuthenticationMethod() ); 175 } 176 177 if ( config.getBindDn() != null ) 178 { 179 env.put( Context.SECURITY_PRINCIPAL, config.getBindDn().toString() ); 180 } 181 182 if ( config.getPassword() != null ) 183 { 184 env.put( Context.SECURITY_CREDENTIALS, config.getPassword() ); 185 } 186 187 // ---------------------------------------------------------------------- 188 // Object Factories 189 // ---------------------------------------------------------------------- 190 191 String objectFactories = null; 192 193 for ( Class<?> objectFactoryClass : config.getObjectFactories() ) 194 { 195 if ( objectFactories == null ) 196 { 197 objectFactories = objectFactoryClass.getName(); 198 } 199 else 200 { 201 objectFactories += ":" + objectFactoryClass.getName(); 202 } 203 } 204 205 if ( objectFactories != null ) 206 { 207 env.setProperty( Context.OBJECT_FACTORIES, objectFactories ); 208 } 209 210 // ---------------------------------------------------------------------- 211 // State Factories 212 // ---------------------------------------------------------------------- 213 214 String stateFactories = null; 215 216 for ( Class<?> stateFactoryClass : config.getStateFactories() ) 217 { 218 if ( stateFactories == null ) 219 { 220 stateFactories = stateFactoryClass.getName(); 221 } 222 else 223 { 224 stateFactories += ":" + stateFactoryClass.getName(); 225 } 226 } 227 228 if ( stateFactories != null ) 229 { 230 env.setProperty( Context.STATE_FACTORIES, stateFactories ); 231 } 232 233 log.debug( "env properties: {}", env ); 234 235 return env; 236 } 237 238 @Override 239 public void close() throws NamingException 240 { 241 if (this.open.compareAndSet( true, false )) 242 { 243 try 244 { 245 if ( context != null ) 246 { 247 context.close( ); 248 } 249 } 250 catch ( NamingException ex ) 251 { 252 log.info( "skip error closing ldap connection {}", ex.getMessage( ) ); 253 throw ex; 254 } 255 finally 256 { 257 this.context = null; 258 } 259 } else { 260 log.warn( "Connection already closed "+this.baseDnRdns ); 261 } 262 } 263 264 // ---------------------------------------------------------------------- 265 // Utils 266 // ---------------------------------------------------------------------- 267 268 @Override 269 public LdapConnectionConfiguration getConfiguration() 270 { 271 return config; 272 } 273 274 @Override 275 public List<Rdn> getBaseDnRdns() 276 { 277 return baseDnRdns; 278 } 279 280 @Override 281 public DirContext getDirContext() 282 { 283 if (this.open.get()) 284 { 285 return context; 286 } else { 287 throw new RuntimeException( "Connection closed " + this.getBaseDnRdns( ) ); 288 } 289 } 290}