/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.usergrid.management;
import net.jcip.annotations.NotThreadSafe;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrSubstitutor;
import org.apache.usergrid.CoreApplication;
import org.apache.usergrid.ServiceITSetup;
import org.apache.usergrid.ServiceITSetupImpl;
import org.apache.usergrid.cassandra.ClearShiroSubject;
import org.apache.usergrid.management.cassandra.ManagementServiceImpl;
import org.apache.usergrid.persistence.EntityManager;
import org.apache.usergrid.persistence.SimpleEntityRef;
import org.apache.usergrid.persistence.entities.Application;
import org.apache.usergrid.persistence.entities.User;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.jvnet.mock_javamail.Mailbox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMultipart;
import java.io.IOException;
import java.util.*;
import static org.apache.commons.lang.StringUtils.isNotBlank;
import static org.apache.usergrid.TestHelper.*;
import static org.apache.usergrid.management.AccountCreationProps.*;
import static org.junit.Assert.*;
import static org.apache.usergrid.management.OrganizationConfigProps.*;
/**
* This test cannot be run concurrently because it changes the management service properties that control how the
* activation workflow is handled and it uses a shared global mock Mailbox to process emails.
* <p/>
* Hence there can be race conditions between test methods in this class.
*/
@NotThreadSafe
public class EmailFlowIT {
private static final Logger logger = LoggerFactory.getLogger( EmailFlowIT.class );
@Rule
public org.apache.usergrid.Application app = new CoreApplication( setup );
@Rule
public ClearShiroSubject clearShiroSubject = new ClearShiroSubject();
@ClassRule
public static ServiceITSetup setup = new ServiceITSetupImpl( );
@Rule
public TestName name = new TestName();
@Test
public void testCreateOrganizationAndAdminWithConfirmationOnly() throws Exception {
setup.set( PROPERTIES_SYSADMIN_APPROVES_ADMIN_USERS, "false" );
setup.set( PROPERTIES_SYSADMIN_APPROVES_ORGANIZATIONS, "false" );
setup.set( PROPERTIES_ADMIN_USERS_REQUIRE_CONFIRMATION, "true" );
setup.set( PROPERTIES_NOTIFY_ADMIN_OF_ACTIVATION, "true" );
final String orgName = uniqueOrg();
final String userName = uniqueUsername();
final String email = uniqueEmail();
OrganizationOwnerInfo org_owner =
createOwnerAndOrganization( orgName, userName, "Test User", email, "testpassword", false, false );
assertNotNull( org_owner );
List<Message> inbox = Mailbox.get( email );
assertFalse( inbox.isEmpty() );
MockImapClient client = new MockImapClient( "mockserver.com", "test-user-1", "somepassword" );
client.processMail();
Message confirmation = inbox.get( 0 );
assertEquals( "User Account Confirmation: " + email, confirmation.getSubject() );
String token = getTokenFromMessage( confirmation );
logger.info( token );
assertEquals( ActivationState.ACTIVATED,
setup.getMgmtSvc().handleConfirmationTokenForAdminUser( org_owner.owner.getUuid(), token ) );
Message activation = inbox.get( 1 );
assertEquals( "User Account Activated", activation.getSubject() );
client = new MockImapClient( "mockserver.com", "test-user-1", "somepassword" );
client.processMail();
}
@Test
public void testCreateOrganizationAndAdminWithConfirmationAndActivation() throws Exception {
setup.set( PROPERTIES_SYSADMIN_APPROVES_ADMIN_USERS, "true" );
setup.set( PROPERTIES_NOTIFY_ADMIN_OF_ACTIVATION, "true" );
setup.set( PROPERTIES_SYSADMIN_APPROVES_ORGANIZATIONS, "false" );
setup.set( PROPERTIES_ADMIN_USERS_REQUIRE_CONFIRMATION, "true" );
final String orgName = uniqueOrg();
final String userName = uniqueUsername();
final String email = uniqueEmail();
OrganizationOwnerInfo org_owner =
createOwnerAndOrganization( orgName, userName, "Test User", email,
"testpassword", false, false );
assertNotNull( org_owner );
List<Message> user_inbox = Mailbox.get( email );
assertFalse( user_inbox.isEmpty() );
Message confirmation = user_inbox.get( 0 );
assertEquals( "User Account Confirmation: "+email, confirmation.getSubject() );
String token = getTokenFromMessage( confirmation );
logger.info( token );
ActivationState state =
setup.getMgmtSvc().handleConfirmationTokenForAdminUser( org_owner.owner.getUuid(), token );
assertEquals( ActivationState.CONFIRMED_AWAITING_ACTIVATION, state );
confirmation = user_inbox.get( 1 );
String body = ( ( MimeMultipart ) confirmation.getContent() ).getBodyPart( 0 ).getContent().toString();
Boolean subbedEmailed = StringUtils.contains( body, "$" );
assertFalse( subbedEmailed );
assertEquals( "User Account Confirmed", confirmation.getSubject() );
List<Message> sysadmin_inbox = Mailbox.get( "testadmin@usergrid.com" );
assertFalse( sysadmin_inbox.isEmpty() );
Message activation = sysadmin_inbox.get( 0 );
assertEquals( "Request For Admin User Account Activation "+email, activation.getSubject() );
token = getTokenFromMessage( activation );
logger.info( token );
state = setup.getMgmtSvc().handleActivationTokenForAdminUser( org_owner.owner.getUuid(), token );
assertEquals( ActivationState.ACTIVATED, state );
Message activated = user_inbox.get( 2 );
assertEquals( "User Account Activated", activated.getSubject() );
MockImapClient client = new MockImapClient( "mockserver.com", "test-user-2", "somepassword" );
client.processMail();
}
@Test
public void skipAllEmailConfiguration() throws Exception {
setup.set( PROPERTIES_SYSADMIN_APPROVES_ORGANIZATIONS, "false" );
setup.set( PROPERTIES_ORGANIZATIONS_REQUIRE_CONFIRMATION, "false" );
setup.set( PROPERTIES_SYSADMIN_APPROVES_ADMIN_USERS, "false" );
setup.set( PROPERTIES_ADMIN_USERS_REQUIRE_CONFIRMATION, "false" );
final String orgName = uniqueOrg();
final String userName = uniqueUsername();
final String email = uniqueEmail();
OrganizationOwnerInfo ooi = setup.getMgmtSvc()
.createOwnerAndOrganization(orgName, userName, "Test User", email,
"testpassword");
EntityManager em = setup.getEmf().getEntityManager( setup.getEmf().getManagementAppId() );
User user = em.get( ooi.getOwner().getUuid(), User.class );
assertTrue( user.activated() );
assertFalse( user.disabled() );
assertTrue( user.confirmed() );
}
@Test
public void testEmailStrings() {
testProperty( PROPERTIES_EMAIL_ADMIN_ACTIVATED, false );
testProperty( PROPERTIES_EMAIL_ADMIN_CONFIRMATION, true );
testProperty( PROPERTIES_EMAIL_ADMIN_PASSWORD_RESET, true );
testProperty( PROPERTIES_EMAIL_ADMIN_USER_ACTIVATION, true );
testProperty( PROPERTIES_EMAIL_ORGANIZATION_ACTIVATED, true );
testProperty( PROPERTIES_EMAIL_ORGANIZATION_CONFIRMATION, true );
testProperty( PROPERTIES_EMAIL_SYSADMIN_ADMIN_ACTIVATION, true );
testProperty( PROPERTIES_EMAIL_SYSADMIN_ORGANIZATION_ACTIVATION, true );
testProperty( PROPERTIES_EMAIL_USER_ACTIVATED, false );
testProperty( PROPERTIES_EMAIL_USER_CONFIRMATION, true );
testProperty( PROPERTIES_EMAIL_USER_PASSWORD_RESET, true );
testProperty( PROPERTIES_EMAIL_USER_PIN_REQUEST, true );
}
/**
* Tests that when a user is added to an app and activation on that app is required, the org admin is emailed
* @throws Exception
*
* TODO, I'm not convinced this worked correctly. IT can't find users collection in the orgs. Therefore,
* I think this is a legitimate bug that was just found from fixing the tests to be unique admins orgs and emails
*/
@Test
public void testAppUserActivationResetpwdMail() throws Exception {
final String orgName = uniqueOrg();
final String appName = uniqueApp();
final String adminUserName = uniqueUsername();
final String adminEmail = uniqueEmail();
final String adminPasswd = "testpassword";
OrganizationOwnerInfo orgOwner = createOwnerAndOrganization(orgName, appName, adminUserName, adminEmail, adminPasswd, false, false);
assertNotNull( orgOwner );
ApplicationInfo app = setup.getMgmtSvc().createApplication( orgOwner.getOrganization().getUuid(), appName );
this.app.waitForQueueDrainAndRefreshIndex();
//turn on app admin approval for app users
enableAdminApproval(app.getId());
final String appUserUsername = uniqueUsername();
final String appUserEmail = uniqueEmail();
OrganizationConfig orgConfig = setup.getMgmtSvc().getOrganizationConfigByUuid(orgOwner.getOrganization().getUuid());
User appUser = setupAppUser( app.getId(), appUserUsername, appUserEmail, false );
String subject = "Request For User Account Activation " + appUserEmail;
String activation_url = orgConfig.getFullUrl(WorkflowUrl.USER_ACTIVATION_URL, orgName, appName,
appUser.getUuid().toString());
setup.refreshIndex(app.getId());
// Activation
setup.getMgmtSvc().startAppUserActivationFlow( app.getId(), appUser );
List<Message> inbox = Mailbox.get( adminEmail );
assertFalse( inbox.isEmpty() );
MockImapClient client = new MockImapClient( "usergrid.com", adminUserName, "somepassword" );
client.processMail();
// subject ok
Message activation = inbox.get( 0 );
assertEquals( subject, activation.getSubject() );
// activation url ok
String mailContent = ( String ) ( ( MimeMultipart ) activation.getContent() ).getBodyPart( 1 ).getContent();
logger.info( mailContent );
assertTrue( StringUtils.contains( mailContent.toLowerCase(), activation_url.toLowerCase() ) );
// token ok
String token = getTokenFromMessage( activation );
logger.info( token );
ActivationState activeState =
setup.getMgmtSvc().handleActivationTokenForAppUser( app.getId(), appUser.getUuid(), token );
assertEquals( ActivationState.ACTIVATED, activeState );
subject = "Password Reset";
String reset_url = orgConfig.getFullUrl(WorkflowUrl.USER_RESETPW_URL, orgName, appName,
appUser.getUuid().toString());
// reset_pwd
setup.getMgmtSvc().startAppUserPasswordResetFlow( app.getId(), appUser );
inbox = Mailbox.get( appUserEmail );
assertFalse( inbox.isEmpty() );
client = new MockImapClient( "test.com", appUserUsername, "somepassword" );
client.processMail();
// subject ok
Message reset = inbox.get( 1 );
assertEquals( subject, reset.getSubject() );
// resetpwd url ok
mailContent = ( String ) ( ( MimeMultipart ) reset.getContent() ).getBodyPart( 1 ).getContent();
logger.info( mailContent );
assertTrue( StringUtils.contains( mailContent.toLowerCase(), reset_url.toLowerCase() ) );
// token ok
token = getTokenFromMessage( reset );
logger.info( token );
assertTrue( setup.getMgmtSvc().checkPasswordResetTokenForAppUser( app.getId(), appUser.getUuid(), token ) );
// ensure revoke works
setup.getMgmtSvc().revokeAccessTokenForAppUser( token );
assertFalse( setup.getMgmtSvc().checkPasswordResetTokenForAppUser( app.getId(), appUser.getUuid(), token ) );
}
/** Tests to make sure a normal user must be activated by the admin after confirmation. */
@Test
public void testAppUserConfirmationMail() throws Exception {
final String orgName = uniqueOrg();
final String appName = uniqueApp();
final String userName = uniqueUsername();
final String email = uniqueEmail();
final String passwd = "testpassword";
OrganizationOwnerInfo orgOwner;
orgOwner = createOwnerAndOrganization( orgName, appName, userName, email, passwd, false, false );
assertNotNull( orgOwner );
setup.getEntityIndex().refresh(app.getId());
ApplicationInfo app = setup.getMgmtSvc().createApplication( orgOwner.getOrganization().getUuid(), appName );
setup.refreshIndex(app.getId());
assertNotNull( app );
enableEmailConfirmation( app.getId() );
enableAdminApproval( app.getId() );
setup.getEntityIndex().refresh(app.getId());
final String appUserEmail = uniqueEmail();
final String appUserUsername = uniqueUsername();
User user = setupAppUser( app.getId(), appUserUsername, appUserEmail, true );
OrganizationConfig orgConfig = setup.getMgmtSvc().getOrganizationConfigByUuid(orgOwner.getOrganization().getUuid());
String subject = "User Account Confirmation: "+appUserEmail;
String confirmation_url = orgConfig.getFullUrl(WorkflowUrl.USER_CONFIRMATION_URL, orgName, appName,
user.getUuid().toString());
// request confirmation
setup.getMgmtSvc().startAppUserActivationFlow( app.getId(), user );
List<Message> inbox = Mailbox.get( appUserEmail );
assertFalse( inbox.isEmpty() );
MockImapClient client = new MockImapClient( "test.com", appUserUsername, "somepassword" );
client.processMail();
// subject ok
Message confirmation = inbox.get( 0 );
assertEquals( subject, confirmation.getSubject() );
// confirmation url ok
String mailContent = ( String ) ( ( MimeMultipart ) confirmation.getContent() ).getBodyPart( 1 ).getContent();
logger.info( mailContent );
assertTrue( StringUtils.contains( mailContent.toLowerCase(), confirmation_url.toLowerCase() ) );
// token ok
String token = getTokenFromMessage( confirmation );
logger.info( token );
ActivationState activeState =
setup.getMgmtSvc().handleConfirmationTokenForAppUser( app.getId(), user.getUuid(), token );
assertEquals( ActivationState.CONFIRMED_AWAITING_ACTIVATION, activeState );
}
/////////////////////////////
// Private Utility Methods //
/////////////////////////////
private OrganizationOwnerInfo createOwnerAndOrganization( String orgName, String userName, String display,
String email, String password, boolean disabled,
boolean active ) throws Exception {
return setup.getMgmtSvc()
.createOwnerAndOrganization( orgName, userName, display, email, password, disabled, active );
}
private String getTokenFromMessage( Message msg ) throws IOException, MessagingException {
String body = ( ( MimeMultipart ) msg.getContent() ).getBodyPart( 0 ).getContent().toString();
// TODO better token extraction
// this is going to get the wrong string if the first part is not
// text/plain and the url isn't the last character in the email
return StringUtils.substringAfterLast( body, "token=" );
}
private void testProperty( String propertyName, boolean containsSubstitution ) {
String propertyValue = setup.get( propertyName );
assertTrue( propertyName + " was not found", isNotBlank( propertyValue ) );
logger.info( propertyName + "=" + propertyValue );
if ( containsSubstitution ) {
Map<String, String> valuesMap = new HashMap<String, String>();
valuesMap.put( "reset_url", "test-url" );
valuesMap.put( "organization_name", "test-org" );
valuesMap.put( "activation_url", "test-url" );
valuesMap.put( "confirmation_url", "test-url" );
valuesMap.put( "user_email", "test-email" );
valuesMap.put( "pin", "test-pin" );
StrSubstitutor sub = new StrSubstitutor( valuesMap );
String resolvedString = sub.replace( propertyValue );
assertNotSame( propertyValue, resolvedString );
}
}
private void enableEmailConfirmation( UUID appId ) throws Exception {
EntityManager em = setup.getEmf().getEntityManager( appId );
SimpleEntityRef ref = new SimpleEntityRef( Application.ENTITY_TYPE, appId );
em.setProperty( ref, ManagementServiceImpl.REGISTRATION_REQUIRES_EMAIL_CONFIRMATION, true );
}
private void enableAdminApproval( UUID appId ) throws Exception {
EntityManager em = setup.getEmf().getEntityManager( appId );
SimpleEntityRef ref = new SimpleEntityRef( Application.ENTITY_TYPE, appId );
em.setProperty( ref, ManagementServiceImpl.REGISTRATION_REQUIRES_ADMIN_APPROVAL, true );
}
private User setupAppUser( UUID appId, String username, String email, boolean activated ) throws Exception {
Mailbox.clearAll();
EntityManager em = setup.getEmf().getEntityManager( appId );
Map<String, Object> userProps = new LinkedHashMap<String, Object>();
userProps.put( "username", username );
userProps.put( "email", email );
userProps.put( "activated", activated );
User user = em.create( User.ENTITY_TYPE, User.class, userProps );
setup.getEntityIndex().refresh(app.getId());
return user;
}
}