Commit 4cc6c617 authored by Alinga Yeung's avatar Alinga Yeung
Browse files

Story 1869. Updated code based on Brian's code review comments. A couple of...

Story 1869. Updated code based on Brian's code review comments. A couple of unit tests are still not working. The mockPersistence object does not seem to throw an exception as expected.
parent 0359a191
Loading
Loading
Loading
Loading
+13 −5
Original line number Diff line number Diff line
@@ -74,12 +74,15 @@ import ca.nrc.cadc.auth.InvalidDelegationTokenException;
import java.net.URI;

/**
 *
 * @author pdowler
 * A class to validate the scope of a delegation token for access control.
 * The scope of a delegation token is composed of the service endpoint
 * and the action allowed with the service endpoint. Both the service endpoint 
 * and the action are validated.
 * @author yeunga
 */
public class ACScopeValidator extends DelegationToken.ScopeValidator
{
    public static final String SCOPE = "ac.resetPassword";
    public static final String RESET_PASSWORD_SCOPE = "/resetPassword";
    
    public ACScopeValidator() { }

@@ -89,11 +92,16 @@ public class ACScopeValidator extends DelegationToken.ScopeValidator
    {
        try
        {
            if (scope.toASCIIString().equals(SCOPE))
            // validate service endpoint
            if (RESET_PASSWORD_SCOPE == requestURI)
            {
                // validate allowed action for this service endpoint
                if (scope.toASCIIString().equals(RESET_PASSWORD_SCOPE))
                {
                    return; // OK
                }
            }
        }
        catch(Exception ignore) { }
        
        throw new InvalidDelegationTokenException("invalid scope: " + scope);
+209 −73
Original line number Diff line number Diff line
@@ -76,12 +76,14 @@ import ca.nrc.cadc.ac.server.UserPersistence;
import ca.nrc.cadc.auth.AuthenticationUtil;
import ca.nrc.cadc.auth.DelegationToken;
import ca.nrc.cadc.auth.HttpPrincipal;
import ca.nrc.cadc.auth.ServletPrincipalExtractor;
import ca.nrc.cadc.log.ServletLogInfo;
import ca.nrc.cadc.util.StringUtil;

import org.apache.log4j.Logger;

import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@@ -94,8 +96,11 @@ import java.security.AccessControlException;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Set;
import java.util.TimeZone;

@@ -111,6 +116,7 @@ public class ResetPasswordServlet extends HttpServlet
{
    private static final Logger log = Logger.getLogger(ResetPasswordServlet.class);

    List<Subject> privilegedSubjects;
    UserPersistence<Principal> userPersistence;

    @Override
@@ -118,12 +124,102 @@ public class ResetPasswordServlet extends HttpServlet
    {
        super.init(config);

        try
        {
            String x500Users = config.getInitParameter(ResetPasswordServlet.class.getName() + ".PrivilegedX500Principals");
            log.debug("privilegedX500Users: " + x500Users);

            String httpUsers = config.getInitParameter(ResetPasswordServlet.class.getName() + ".PrivilegedHttpPrincipals");
            log.debug("privilegedHttpUsers: " + httpUsers);

            String[] x500List = new String[0];
            String[] httpList = new String[0];
            if (x500Users != null && httpUsers != null)
            {
                x500List = x500Users.split(" ");
                httpList = httpUsers.split(" ");
            }

            if (x500List.length != httpList.length)
            {
                throw new RuntimeException("Init exception: Lists of augment subject principals not equivalent in length");
            }

            privilegedSubjects = new ArrayList<Subject>(x500Users.length());
            for (int i=0; i<x500List.length; i++)
            {
                Subject s = new Subject();
                s.getPrincipals().add(new X500Principal(x500List[i]));
                s.getPrincipals().add(new HttpPrincipal(httpList[i]));
                privilegedSubjects.add(s);
            }

            PluginFactory pluginFactory = new PluginFactory();
            userPersistence = pluginFactory.createUserPersistence();
        }
        catch (Throwable t)
        {
            log.fatal("Error initializing group persistence", t);
            throw new ExceptionInInitializerError(t);
        }
    }
    
    protected boolean isPrivilegedSubject(final HttpServletRequest request)
    {        
        if (privilegedSubjects == null || privilegedSubjects.isEmpty())
        {
            return false;
        }
        
        log.debug("alinga-- invoking ServletPrincipalExtractor");
        ServletPrincipalExtractor extractor = new ServletPrincipalExtractor(request);
        log.debug("alinga-- done invoking ServletPrincipalExtractor");
        Set<Principal> principals = extractor.getPrincipals();
        log.debug("alinga-- principals size = " + principals.size());

        for (Principal principal : principals)
        {
            if (principal instanceof X500Principal)
            {
                for (Subject s : privilegedSubjects)
                {
                    Set<X500Principal> x500Principals = s.getPrincipals(X500Principal.class);
                    for (X500Principal p2 : x500Principals)
                    {
                        log.debug("alinga-- p2 x500 name = " + p2.getName());
                        log.debug("alinga-- principal x500 name = " + principal.getName());

                        if (p2.getName().equalsIgnoreCase(principal.getName()))
                        {
                            return true;
                        }
                    }
                }
            }

            if (principal instanceof HttpPrincipal)
            {
                for (Subject s : privilegedSubjects)
                {
                    Set<HttpPrincipal> httpPrincipals = s.getPrincipals(HttpPrincipal.class);
                    for (HttpPrincipal p2 : httpPrincipals)
                    {
                        log.debug("alinga-- p2 http name = " + p2.getName());
                        log.debug("alinga-- principal http name = " + principal.getName());
                        if (p2.getName().equalsIgnoreCase(principal.getName()))
                        {
                            return true;
                        }
                    }
                }
            }
        }

        return false;
    }
    
    /**
     * Handle a /ac GET operation. The subject provided is expected to be servops.
     * Handle a /ac GET operation. The subject provided is expected to be a privileged user.
     *
     * @param request  The HTTP Request.
     * @param response The HTTP Response.
@@ -148,6 +244,8 @@ public class ResetPasswordServlet extends HttpServlet
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            }
            else
            {
                if (isPrivilegedSubject(request))
                {
                    String token = (String) Subject.doAs(subject, new PrivilegedExceptionAction<Object>()
                    {
@@ -158,8 +256,8 @@ public class ResetPasswordServlet extends HttpServlet
                            {
                                User<Principal> user = userPersistence.getUserByEmailAddress(emailAddress);
                                HttpPrincipal userID = (HttpPrincipal) user.getUserID();
                            URI scopeURI = new URI(ACScopeValidator.SCOPE);
                            int duration = 2; // hours
                                URI scopeURI = new URI(ACScopeValidator.RESET_PASSWORD_SCOPE);
                                int duration = 24; // hours
                                Calendar expiry = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
                                expiry.add(Calendar.HOUR, duration);
                                DelegationToken dt = new DelegationToken(userID, scopeURI, expiry.getTime());
@@ -177,17 +275,32 @@ public class ResetPasswordServlet extends HttpServlet
                    response.setContentLength(token.length());
                    response.getWriter().write(token);
                }
                else
                {
                    logInfo.setMessage("Permission denied subject");
                    response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                }
            }
        }
        catch (PrivilegedActionException pae)
        catch (Throwable t)
        {
            Exception e = pae.getException();
            try
            {
                if (t instanceof PrivilegedActionException)
                {
                    Exception e = ((PrivilegedActionException) t).getException();
                    if (e != null)
                    {
                String msg = e.getMessage();
                log.debug(msg, e);
                logInfo.setMessage(msg);
                if (e instanceof UserNotFoundException)
                        throw e;
                    }
                }
                
                throw t;
            }
            catch (UserNotFoundException e)
            {
                log.debug(e.getMessage(), e);
                logInfo.setMessage(e.getMessage());
                if (e.getMessage().contains("More than one user"))
                {
                    response.setStatus(HttpServletResponse.SC_CONFLICT);
@@ -197,19 +310,19 @@ public class ResetPasswordServlet extends HttpServlet
                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                }
            }
                else
            catch (IllegalArgumentException e)
            {
                log.debug(e.getMessage(), e);
                logInfo.setMessage(e.getMessage());
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            }
            }
        }
        catch (IllegalArgumentException e)
            catch (AccessControlException e)
            {
                log.debug(e.getMessage(), e);
                logInfo.setMessage(e.getMessage());
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            }
        catch (Throwable t)
            catch (Throwable t1)
            {
                String message = "Internal Server Error: " + t.getMessage();
                log.error(message, t);
@@ -217,6 +330,7 @@ public class ResetPasswordServlet extends HttpServlet
                logInfo.setMessage(message);
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        }
        finally
        {
            logInfo.setElapsedTime(System.currentTimeMillis() - start);
@@ -277,6 +391,27 @@ public class ResetPasswordServlet extends HttpServlet
                });
            }
        }
        catch (Throwable t)
        {
            try
            {
                if (t instanceof PrivilegedActionException)
                {
                    Exception e = ((PrivilegedActionException) t).getException();
                    if (e != null)
                    {
                        throw e;
                    }
                }
                
                throw t;
            }
            catch (UserNotFoundException e)
            {
                log.debug(e.getMessage(), e);
                logInfo.setMessage(e.getMessage());
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            }
            catch (IllegalArgumentException e)
            {
                log.debug(e.getMessage(), e);
@@ -289,7 +424,7 @@ public class ResetPasswordServlet extends HttpServlet
                logInfo.setMessage(e.getMessage());
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            }
        catch (Throwable t)
            catch (Throwable t1)
            {
                String message = "Internal Server Error: " + t.getMessage();
                log.error(message, t);
@@ -297,6 +432,7 @@ public class ResetPasswordServlet extends HttpServlet
                logInfo.setMessage(message);
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        }
        finally
        {
            logInfo.setElapsedTime(System.currentTimeMillis() - start);
+120 −35
Original line number Diff line number Diff line
@@ -69,8 +69,10 @@
package ca.nrc.cadc.ac.server.web;

import ca.nrc.cadc.ac.User;
import ca.nrc.cadc.ac.UserNotFoundException;
import ca.nrc.cadc.ac.server.ACScopeValidator;
import ca.nrc.cadc.ac.server.UserPersistence;
import ca.nrc.cadc.ac.server.ldap.LdapUserDAO;
import ca.nrc.cadc.auth.DelegationToken;
import ca.nrc.cadc.auth.HttpPrincipal;
import ca.nrc.cadc.util.RsaSignatureGenerator;
@@ -93,8 +95,10 @@ import java.net.URLDecoder;
import java.security.Principal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.TimeZone;

import static org.easymock.EasyMock.*;
@@ -148,54 +152,46 @@ public class ResetPasswordServletTest
    }
        
    @Test
    public void testGetDelegationTokenWithNullSubject() throws Exception
    public void testGetWithNullSubject() throws Exception
    {
        final Subject subject = null;
        testSubjectAndEmailAddress(subject, "testEmail@canada.ca", HttpServletResponse.SC_UNAUTHORIZED);
    }
        
    @Test
    public void testGetDelegationTokenWithEmptySubject() throws Exception
    public void testGetWithEmptySubject() throws Exception
    {
        final Subject subject = new Subject();;
        testSubjectAndEmailAddress(subject, "email@canada.ca", HttpServletResponse.SC_UNAUTHORIZED);
    }
    
    @Test
    public void testGetDelegationTokenWithMissingEmailAddress() throws Exception
    public void testPrivilegedSubjectAndEmailAddress(final List<Subject> privlegedSubjects,
            final Subject subject, int responseStatus, 
            final UserPersistence<Principal> mockUserPersistence) throws Exception
    {
        final Subject subject = new Subject();;
        subject.getPrincipals().add(new HttpPrincipal("CADCtest"));
        testSubjectAndEmailAddress(subject, "", HttpServletResponse.SC_BAD_REQUEST);
    }
    
    @Test
    public void testResetPasswordWithInternalServerError() throws Exception
    {
        DelegationToken dt = null;
        
        final String emailAddress = "email@canada.ca";
        HttpPrincipal userID = new HttpPrincipal("CADCtest");
        
        final UserPersistence<Principal> mockUserPersistence =
                createMock(UserPersistence.class);
        mockUserPersistence.getUserByEmailAddress(emailAddress);
        expectLastCall().andThrow(new RuntimeException());

        final Subject subject = new Subject();
        subject.getPrincipals().add(userID);
    
        @SuppressWarnings("serial")
        final ResetPasswordServlet testSubject = new ResetPasswordServlet()
        {
            @Override
            public void init(final ServletConfig config) throws ServletException
            public void init() throws ServletException
            {
                super.init();

                privilegedSubjects = privlegedSubjects;
                userPersistence = mockUserPersistence;
            }
            
            @Override
            protected boolean isPrivilegedSubject(final HttpServletRequest request)
            {
                if (privlegedSubjects == null || privlegedSubjects.isEmpty())
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }
            
            @Override
            Subject getSubject(final HttpServletRequest request)
            {
@@ -211,18 +207,30 @@ public class ResetPasswordServletTest
        expect(mockRequest.getPathInfo()).andReturn("users/CADCtest").once();
        expect(mockRequest.getMethod()).andReturn("POST").once();
        expect(mockRequest.getRemoteAddr()).andReturn("mysite.com").once();
        expect(mockRequest.getParameter("emailAddress")).andReturn(emailAddress).once();
        
        mockResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        if (privlegedSubjects != null && !privlegedSubjects.isEmpty())
        {
            if (mockUserPersistence == null)
            {
                expect(mockRequest.getParameter("emailAddress")).andReturn("").once();
            }
            else
            {
                expect(mockRequest.getParameter("emailAddress")).andReturn("email@canada.ca").once();
            }
        }
        
        mockResponse.setStatus(responseStatus);
        expectLastCall().once();
    
        replay(mockRequest, mockResponse, mockUserPersistence);
        replay(mockRequest, mockResponse);
    
        Subject.doAs(subject, new PrivilegedExceptionAction<Void>()
        {
            @Override
            public Void run() throws Exception
            {
                testSubject.init();
                testSubject.doGet(mockRequest, mockResponse);
                return null;
            }
@@ -230,4 +238,81 @@ public class ResetPasswordServletTest
    
        verify(mockRequest, mockResponse);
    }
    
    @Test
    public void testGetWithNullPrivilegedSubjects() throws Exception
    {
        final Subject subject = new Subject();;
        subject.getPrincipals().add(new HttpPrincipal("CADCtest"));
        testPrivilegedSubjectAndEmailAddress(null, subject, 
                HttpServletResponse.SC_FORBIDDEN, null);
    }
     
    @Test
    public void testGetWithEmptyPrivilegedSubjects() throws Exception
    {
        final Subject subject = new Subject();;
        subject.getPrincipals().add(new HttpPrincipal("CADCtest"));
        testPrivilegedSubjectAndEmailAddress(new ArrayList<Subject>(), subject, 
                HttpServletResponse.SC_FORBIDDEN, null);
    }
      
    @Test
    public void testGetWithMissingEmailAddress() throws Exception
    {
        final Subject subject = new Subject();;
        subject.getPrincipals().add(new HttpPrincipal("CADCtest"));
        List<Subject> privilegedSubjects = new ArrayList<Subject>();
        privilegedSubjects.add(new Subject());
        testPrivilegedSubjectAndEmailAddress(privilegedSubjects, subject, 
                HttpServletResponse.SC_BAD_REQUEST, null);
    }
    
    @SuppressWarnings("unchecked")
    @Test
    public void testGetWithMoreThanOneUserFound() throws Exception
    {
        final Subject subject = new Subject();;
        subject.getPrincipals().add(new HttpPrincipal("CADCtest"));
        List<Subject> privilegedSubjects = new ArrayList<Subject>();
        privilegedSubjects.add(new Subject());
        UserPersistence<Principal> mockUserPersistence = 
                (UserPersistence<Principal>) createMock(UserPersistence.class);
        UserNotFoundException unfe = new UserNotFoundException(LdapUserDAO.EMAIL_ADDRESS_CONFLICT_MESSAGE);
        expect(mockUserPersistence.getUserByEmailAddress("email@canadata.ca")).andThrow(unfe);
        testPrivilegedSubjectAndEmailAddress(privilegedSubjects, subject, 
                HttpServletResponse.SC_CONFLICT, mockUserPersistence);
    }
    
    @SuppressWarnings("unchecked")
    @Test
    public void testGetWithNoUserFound() throws Exception
    {
        final Subject subject = new Subject();;
        subject.getPrincipals().add(new HttpPrincipal("CADCtest"));
        List<Subject> privilegedSubjects = new ArrayList<Subject>();
        privilegedSubjects.add(new Subject());
        UserPersistence<Principal> mockUserPersistence = 
                (UserPersistence<Principal>) createMock(UserPersistence.class);
        UserNotFoundException unfe = new UserNotFoundException("User with email address ");
        expect(mockUserPersistence.getUserByEmailAddress("email@canadata.ca")).andThrow(unfe);
        testPrivilegedSubjectAndEmailAddress(privilegedSubjects, subject, 
                HttpServletResponse.SC_NOT_FOUND, mockUserPersistence);
    }
    
    @SuppressWarnings("unchecked")
    @Test
    public void testGetWithInternalServerError() throws Exception
    {
        final Subject subject = new Subject();;
        subject.getPrincipals().add(new HttpPrincipal("CADCtest"));
        List<Subject> privilegedSubjects = new ArrayList<Subject>();
        privilegedSubjects.add(new Subject());
        UserPersistence<Principal> mockUserPersistence = 
                (UserPersistence<Principal>) createMock(UserPersistence.class);
        RuntimeException rte = new RuntimeException();
        expect(mockUserPersistence.getUserByEmailAddress("email@canadata.ca")).andThrow(rte);
        testPrivilegedSubjectAndEmailAddress(privilegedSubjects, subject, 
                HttpServletResponse.SC_INTERNAL_SERVER_ERROR, mockUserPersistence);
    }
}