<type>xml</type>
<scope>runtime</scope>
</dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-odl-plugin</artifactId>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netconf-api</artifactId>
<!-- FIXME: This introduces cycle between projects, which makes version updates
harder. Should be moved to different.
-->
- <feature version='${aaa.version}'>odl-aaa-netconf-plugin</feature>
+ <feature version='${project.version}'>odl-aaa-netconf-plugin</feature>
<bundle>mvn:org.opendaylight.netconf/netconf-ssh/${project.version}</bundle>
</feature>
<bundle>mvn:org.opendaylight.netconf/mdsal-netconf-monitoring/${project.version}</bundle>
<configfile finalname='${config.configfile.directory}/${config.netconf.mdsal.configfile}'>mvn:org.opendaylight.netconf/netconf-mdsal-config/${project.version}/xml/config</configfile>
</feature>
+
+ <feature name='odl-aaa-netconf-plugin' description='OpenDaylight :: AAA :: ODL NETCONF Plugin'
+ version='${project.version}'>
+ <feature version='${config.version}'>odl-config-api</feature>
+ <bundle>mvn:org.opendaylight.netconf/netconf-auth/${project.version}</bundle>
+ <feature version='${aaa.version}'>odl-aaa-authn</feature>
+ <bundle>mvn:org.opendaylight.netconf/aaa-authn-odl-plugin/${project.version}</bundle>
+ </feature>
+
+ <feature name='odl-aaa-netconf-plugin-no-cluster'
+ description='OpenDaylight :: AAA :: ODL NETCONF Plugin - NO CLUSTER'
+ version='${project.version}'>
+ <feature version='${config.version}'>odl-config-api</feature>
+ <bundle>mvn:org.opendaylight.netconf/netconf-auth/${project.version}</bundle>
+ <feature version='${aaa.version}'>odl-aaa-authn-no-cluster</feature>
+ <bundle>mvn:org.opendaylight.netconf/aaa-authn-odl-plugin/${project.version}</bundle>
+ </feature>
</features>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ ~
+ ~ This program and the accompanying materials are made available under the
+ ~ terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ ~ and is available at http://www.eclipse.org/legal/epl-v10.html
+ -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.netconf</groupId>
+ <artifactId>netconf-subsystem</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-odl-plugin</artifactId>
+ <packaging>bundle</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.netconf</groupId>
+ <artifactId>netconf-auth</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <groupId>org.opendaylight.yangtools</groupId>
+ <artifactId>yang-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+</project>
--- /dev/null
+/*
+ * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.aaa.odl;
+
+import java.util.Map;
+import org.opendaylight.aaa.api.AuthenticationException;
+import org.opendaylight.aaa.api.Claim;
+import org.opendaylight.aaa.api.CredentialAuth;
+import org.opendaylight.aaa.api.PasswordCredentials;
+import org.opendaylight.netconf.auth.AuthProvider;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * AuthProvider implementation delegating to AAA CredentialAuth<PasswordCredentials> instance.
+ */
+public final class CredentialServiceAuthProvider implements AuthProvider, AutoCloseable {
+ private static final Logger logger = LoggerFactory.getLogger(CredentialServiceAuthProvider.class);
+
+ /**
+ * Singleton instance with delayed instantiation
+ */
+ public static volatile Map.Entry<BundleContext, CredentialServiceAuthProvider> INSTANCE;
+
+ // TODO what domain should be used for this ? can we leave null ?
+ public static final String DOMAIN = null;
+
+ // FIXME CredentialAuth is generic and it causes warnings during compilation
+ // Maybe there should be a PasswordCredentialAuth implements CredentialAuth<PasswordCredentials>
+ private volatile CredentialAuth<PasswordCredentials> nullableCredService;
+ private final ServiceTracker<CredentialAuth, CredentialAuth> listenerTracker;
+
+ public CredentialServiceAuthProvider(final BundleContext bundleContext) {
+
+ final ServiceTrackerCustomizer<CredentialAuth, CredentialAuth> customizer = new ServiceTrackerCustomizer<CredentialAuth, CredentialAuth>() {
+ @Override
+ public CredentialAuth addingService(final ServiceReference<CredentialAuth> reference) {
+ logger.trace("Credential service {} added", reference);
+ nullableCredService = bundleContext.getService(reference);
+ return nullableCredService;
+ }
+
+ @Override
+ public void modifiedService(final ServiceReference<CredentialAuth> reference, final CredentialAuth service) {
+ logger.trace("Replacing modified Credential service {}", reference);
+ nullableCredService = service;
+ }
+
+ @Override
+ public void removedService(final ServiceReference<CredentialAuth> reference, final CredentialAuth service) {
+ logger.trace("Removing Credential service {}. This AuthProvider will fail to authenticate every time", reference);
+ synchronized (CredentialServiceAuthProvider.this) {
+ nullableCredService = null;
+ }
+ }
+ };
+ listenerTracker = new ServiceTracker<>(bundleContext, CredentialAuth.class, customizer);
+ listenerTracker.open();
+ }
+
+ /**
+ * Authenticate user. This implementation tracks CredentialAuth<PasswordCredentials> and delegates the decision to it. If the service is not
+ * available, IllegalStateException is thrown.
+ */
+ @Override
+ public synchronized boolean authenticated(final String username, final String password) {
+ if (nullableCredService == null) {
+ logger.warn("Cannot authenticate user '{}', Credential service is missing", username);
+ throw new IllegalStateException("Credential service is not available");
+ }
+
+ Claim claim;
+ try {
+ claim = nullableCredService.authenticate(new PasswordCredentialsWrapper(username, password), DOMAIN);
+ } catch (AuthenticationException e) {
+ logger.debug("Authentication failed for user '{}' : {}", username, e);
+ return false;
+ }
+
+ logger.debug("Authentication result for user '{}' : {}", username, claim.domain());
+ return true;
+ }
+
+ @Override
+ public void close() throws Exception {
+ listenerTracker.close();
+ nullableCredService = null;
+ }
+
+ private static final class PasswordCredentialsWrapper implements PasswordCredentials {
+ private final String username;
+ private final String password;
+
+ public PasswordCredentialsWrapper(final String username, final String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ @Override
+ public String username() {
+ return username;
+ }
+
+ @Override
+ public String password() {
+ return password;
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.yang.gen.v1.config.aaa.authn.netconf.plugin.rev150715;
+
+import com.google.common.base.Preconditions;
+import org.opendaylight.aaa.odl.CredentialServiceAuthProvider;
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.osgi.framework.BundleContext;
+
+public class AuthProviderModule extends org.opendaylight.yang.gen.v1.config.aaa.authn.netconf.plugin.rev150715.AbstractAuthProviderModule {
+
+ private BundleContext bundleContext;
+
+ public AuthProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
+ super(identifier, dependencyResolver);
+ }
+
+ public AuthProviderModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier, org.opendaylight.controller.config.api.DependencyResolver dependencyResolver, org.opendaylight.yang.gen.v1.config.aaa.authn.netconf.plugin.rev150715.AuthProviderModule oldModule, AutoCloseable oldInstance) {
+ super(identifier, dependencyResolver, oldModule, oldInstance);
+ }
+
+ public AuthProviderModule(final ModuleIdentifier moduleIdentifier, final DependencyResolver dependencyResolver, final AuthProviderModule oldModule, final AutoCloseable oldInstance, final BundleContext bundleContext) {
+ this(moduleIdentifier, dependencyResolver, oldModule, oldInstance);
+ this.bundleContext = bundleContext;
+ }
+
+ public AuthProviderModule(final ModuleIdentifier moduleIdentifier, final DependencyResolver dependencyResolver, final BundleContext bundleContext) {
+ this(moduleIdentifier, dependencyResolver);
+ this.bundleContext = bundleContext;
+ }
+
+ @Override
+ public void customValidation() {
+ Preconditions.checkNotNull(bundleContext, "BundleContext was not properly set up");
+ }
+
+ @Override
+ public AutoCloseable createInstance() {
+ return new CredentialServiceAuthProvider(bundleContext);
+ }
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+/*
+* Generated file
+*
+* Generated from: yang module name: aaa-authn-netconf-plugin yang module local name: aaa-authn-netconf-plugin
+* Generated by: org.opendaylight.controller.config.yangjmxgenerator.plugin.JMXGenerator
+* Generated at: Wed Jul 15 15:16:51 CEST 2015
+*
+* Do not modify this file unless it is present under src/main directory
+*/
+package org.opendaylight.yang.gen.v1.config.aaa.authn.netconf.plugin.rev150715;
+
+import java.util.Collections;
+import java.util.Set;
+import org.opendaylight.controller.config.api.DependencyResolver;
+import org.opendaylight.controller.config.api.DependencyResolverFactory;
+import org.opendaylight.controller.config.api.ModuleIdentifier;
+import org.osgi.framework.BundleContext;
+
+public class AuthProviderModuleFactory extends org.opendaylight.yang.gen.v1.config.aaa.authn.netconf.plugin.rev150715.AbstractAuthProviderModuleFactory {
+
+ private static final ModuleIdentifier DEFAULT_INSTANCE_ID = new ModuleIdentifier(NAME, "default-auth-provider");
+
+ @Override
+ public AuthProviderModule instantiateModule(final String instanceName, final DependencyResolver dependencyResolver, final AuthProviderModule oldModule, final AutoCloseable oldInstance, final BundleContext bundleContext) {
+ return new AuthProviderModule(
+ new ModuleIdentifier(NAME, instanceName), dependencyResolver, oldModule, oldInstance, bundleContext);
+ }
+
+ @Override
+ public AuthProviderModule instantiateModule(final String instanceName, final DependencyResolver dependencyResolver, final BundleContext bundleContext) {
+ return new AuthProviderModule(
+ new ModuleIdentifier(NAME, instanceName), dependencyResolver, bundleContext);
+ }
+
+ @Override
+ public Set<AuthProviderModule> getDefaultModules(final DependencyResolverFactory dependencyResolverFactory, final BundleContext bundleContext) {
+ // Config subsystem puts this instance into OSGi service registry automatically
+ final DependencyResolver dependencyResolver = dependencyResolverFactory.createDependencyResolver(DEFAULT_INSTANCE_ID);
+ return Collections.singleton(new AuthProviderModule(DEFAULT_INSTANCE_ID, dependencyResolver, bundleContext));
+ }
+}
--- /dev/null
+module aaa-authn-netconf-plugin {
+
+ yang-version 1;
+ namespace "config:aaa:authn:netconf:plugin";
+ prefix "aaa-authn-store-cfg";
+
+ import config { prefix config; revision-date 2013-04-05; }
+ import netconf-auth { prefix na; revision-date 2015-07-15; }
+
+ revision "2015-07-15" {
+ description
+ "Initial revision.";
+ }
+
+ identity aaa-authn-netconf-plugin {
+ base config:module-type;
+ config:java-name-prefix AuthProvider;
+ config:provided-service na:netconf-auth-provider;
+ }
+
+ augment "/config:modules/config:module/config:configuration" {
+ case aaa-authn-netconf-plugin {
+ when "/config:modules/config:module/config:type = 'aaa-authn-netconf-plugin'";
+ // no config yet
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.aaa.odl;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.opendaylight.aaa.api.AuthenticationException;
+import org.opendaylight.aaa.api.Claim;
+import org.opendaylight.aaa.api.CredentialAuth;
+import org.opendaylight.aaa.api.PasswordCredentials;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Filter;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+public class CredentialServiceAuthProviderTest {
+
+ @Mock
+ private CredentialAuth<PasswordCredentials> credAuth;
+ @Mock
+ private BundleContext ctx;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ doReturn(mock(Filter.class)).when(ctx).createFilter(anyString());
+ }
+
+ @Test(expected = IllegalStateException.class)
+ public void testAuthenticatedNoDelegate() throws Exception {
+ CredentialServiceAuthProvider credentialServiceAuthProvider = new CredentialServiceAuthProvider(ctx);
+ credentialServiceAuthProvider.authenticated("user", "pwd");
+ }
+
+ @Test
+ public void testAuthenticatedTrue() throws Exception {
+ ServiceReference serviceRef = mock(ServiceReference.class);
+
+ ServiceListenerAnswer answer = new ServiceListenerAnswer();
+ doAnswer(answer).when(ctx).addServiceListener(any(ServiceListener.class), anyString());
+
+ Claim claim = mock(Claim.class);
+ doReturn("domain").when(claim).domain();
+ doReturn(claim).when(credAuth).authenticate(any(PasswordCredentials.class), anyString());
+
+ doReturn(credAuth).when(ctx).getService(serviceRef);
+ CredentialServiceAuthProvider credentialServiceAuthProvider = new CredentialServiceAuthProvider(ctx);
+
+ answer.serviceListener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, serviceRef));
+ assertNotNull(answer.serviceListener);
+
+ assertTrue(credentialServiceAuthProvider.authenticated("user", "pwd"));
+ }
+
+ @Test
+ public void testAuthenticatedFalse() throws Exception {
+ ServiceReference serviceRef = mock(ServiceReference.class);
+
+ ServiceListenerAnswer answer = new ServiceListenerAnswer();
+ doAnswer(answer).when(ctx).addServiceListener(any(ServiceListener.class), anyString());
+
+ doThrow(AuthenticationException.class).when(credAuth).authenticate(any(PasswordCredentials.class), anyString());
+
+ doReturn(credAuth).when(ctx).getService(serviceRef);
+ CredentialServiceAuthProvider credentialServiceAuthProvider = new CredentialServiceAuthProvider(ctx);
+
+ answer.serviceListener.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, serviceRef));
+ assertNotNull(answer.serviceListener);
+
+ assertFalse(credentialServiceAuthProvider.authenticated("user", "pwd"));
+ }
+
+ private static class ServiceListenerAnswer implements Answer {
+
+ ServiceListener serviceListener;
+
+ @Override
+ public Object answer(final InvocationOnMock invocationOnMock) throws Throwable {
+ serviceListener = (ServiceListener) invocationOnMock.getArguments()[0];
+ return null;
+ }
+ }
+}
<dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>aaa-authn-odl-plugin</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>netconf-config-dispatcher</artifactId>
<module>netconf-connector-config</module>
<module>netconf-mdsal-config</module>
<module>netconf-auth</module>
+ <module>aaa-authn-odl-plugin</module>
<module>netconf-notifications-impl</module>
<module>netconf-notifications-api</module>
<module>sal-netconf-connector</module>
<dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-artifacts</artifactId>
+ <version>${aaa.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
<dependency>
<groupId>org.opendaylight.controller</groupId>
<artifactId>config-artifacts</artifactId>