package codesample;
/*
* Copyright (c) 2013 Research In Motion Limited.
*
* Licensed 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.
*/
import java.io.IOException;
import java.io.PrintStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.PrivilegedActionException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.WebServiceException;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transport.http.HTTPException;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import com.rim.ws.enterprise.admin.Authenticator;
import com.rim.ws.enterprise.admin.BWS;
import com.rim.ws.enterprise.admin.BWSService;
import com.rim.ws.enterprise.admin.BWSUtil;
import com.rim.ws.enterprise.admin.BWSUtilService;
import com.rim.ws.enterprise.admin.CredentialType;
import com.rim.ws.enterprise.admin.EchoRequest;
import com.rim.ws.enterprise.admin.EchoResponse;
import com.rim.ws.enterprise.admin.GetAuthenticatorsRequest;
import com.rim.ws.enterprise.admin.GetAuthenticatorsResponse;
import com.rim.ws.enterprise.admin.GetEncodedUsernameRequest;
import com.rim.ws.enterprise.admin.GetEncodedUsernameResponse;
import com.rim.ws.enterprise.admin.RequestMetadata;
import com.rim.ws.enterprise.admin.ResponseMetadata;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
/*
* AuthenticationSample.java
*
* A program that demonstrates the different methods of authentication for
* BlackBerry Web Services (BWS) for Enterprise Administration APIs.
*
* This sample program demonstrates how to make an authenticated API call
* using one of three options:
* 1) BlackBerry Administration Service credentials
* 2) Active Directory Credentials
* 3) Single Sign On using the currently logged in user's credentials
* Tested with Apache CXF 2.7.3, recommended to use Apache CXF 2.7.3 or later.
*
* This program was tested against the BlackBerry Enterprise Service 10
* version 10.1
*/
public class AuthenticationSample {
// Values used in log messages.
final private static long NANOSECONDS_IN_A_MILLISECOND = 1000000L;
final private static float NANOSECONDS_IN_A_SECOND = 1000 * NANOSECONDS_IN_A_MILLISECOND;
final private static long startTime = System.nanoTime();
// Web service stubs.
private static BWSService _bwsService;
private static BWS _bws;
private static BWSUtilService _bwsUtilService;
private static BWSUtil _bwsUtil;
// The request Metadata information. This is the version of the WSDL used to generate the proxy,
// not the version of the server.
private final static String CLIENT_VERSION = "<Client Version>"; // e.g. CLIENT_VERSION = "6.2.0"
/*
* To use a different locale, call getLocales() in the BWSUtilService web service to see which locales are
* supported.
*/
private final static String LOCALE = "en_US";
private final static String ORG_UID = "0";
private final static RequestMetadata REQUEST_METADATA = new RequestMetadata();
/*****************************************************************************************************************
*
* Get the authenticator object for the authenticator name.
*
* @param authenticatorName A string containing the name of the desired authenticator.
* @return Returns the requested authenticator if it is found, null otherwise.
*
*****************************************************************************************************************
*/
public static Authenticator getAuthenticator(String authenticatorName) throws WebServiceException {
final String METHOD_NAME = "getAuthenticator()";
final String BWS_API_NAME = "_bwsUtil.getAuthenticators()";
logMessage("Entering %s", METHOD_NAME);
Authenticator returnValue = null;
GetAuthenticatorsRequest request = new GetAuthenticatorsRequest();
request.setMetadata(REQUEST_METADATA);
GetAuthenticatorsResponse response = null;
try {
logRequest(BWS_API_NAME);
response = _bwsUtil.getAuthenticators(request);
logResponse(BWS_API_NAME, response.getReturnStatus().getCode(), response.getMetadata());
} catch (WebServiceException e) {
// Log and re-throw exception.
logMessage("Exiting %s with exception \"%s\"", METHOD_NAME, e.getMessage());
throw e;
}
if (response.getReturnStatus().getCode().equals("SUCCESS")) {
if (response.getAuthenticators() != null && !response.getAuthenticators().isEmpty()) {
for (Authenticator authenticator : response.getAuthenticators()) {
if (authenticator.getName().equalsIgnoreCase(authenticatorName)) {
returnValue = authenticator;
break;
}
}
if (returnValue == null) {
logMessage("Could not find \"%s\" in GetAuthenticatorsResponse", authenticatorName);
}
} else {
logMessage("No authenticators in GetAuthenticatorsResponse");
}
} else {
logMessage("Error Message: \"%s\"", response.getReturnStatus().getMessage());
}
logMessage("Exiting %s with %s", METHOD_NAME, returnValue == null ? "\"null\""
: "Authenticator object (Name \"" + returnValue.getName() + "\")");
return returnValue;
}
/*****************************************************************************************************************
*
* Get the encoded username required to authenticate user to BWS.
*
* @return Returns a string containing the encoded username if successful, and null otherwise.
*
*****************************************************************************************************************
*/
public static String getEncodedUserName(String username, Authenticator authenticator,
CredentialType credentialType, String domain) throws WebServiceException {
final String METHOD_NAME = "getEncodedUserName()";
final String BWS_API_NAME = "_bwsUtil.getEncodedUsername()";
logMessage("Entering %s", METHOD_NAME);
String returnValue = null;
GetEncodedUsernameRequest request = new GetEncodedUsernameRequest();
request.setMetadata(REQUEST_METADATA);
request.setUsername(username);
request.setOrgUid(REQUEST_METADATA.getOrganizationUid());
request.setAuthenticator(authenticator);
request.setCredentialType(credentialType);
request.setDomain(domain);
GetEncodedUsernameResponse response = null;
try {
logRequest(BWS_API_NAME);
response = _bwsUtil.getEncodedUsername(request);
logResponse(BWS_API_NAME, response.getReturnStatus().getCode(), response.getMetadata());
} catch (WebServiceException e) {
// Log and re-throw exception.
logMessage("Exiting %s with exception \"%s\"", METHOD_NAME, e.getMessage());
throw e;
}
if (response.getReturnStatus().getCode().equals("SUCCESS")) {
returnValue = response.getEncodedUsername();
} else {
logMessage("Error Message: \"%s\"", response.getReturnStatus().getMessage());
}
logMessage("Exiting %s", METHOD_NAME);
return returnValue;
}
/*****************************************************************************************************************
*
* Perform a call to _bws.echo().
*
* @return Returns true if echo is successful, and false otherwise.
*
*****************************************************************************************************************
*/
public static boolean echo() throws WebServiceException {
final String METHOD_NAME = "echo()";
final String BWS_API_NAME = "_bws.echo()";
logMessage("Entering %s", METHOD_NAME);
boolean returnValue = true;
EchoRequest request = new EchoRequest();
EchoResponse response = null;
request.setMetadata(REQUEST_METADATA);
request.setText("Hello World!");
try {
logRequest(BWS_API_NAME);
response = _bws.echo(request);
logResponse(BWS_API_NAME, response.getReturnStatus().getCode(), response.getMetadata());
} catch (WebServiceException e) {
if (e.getCause() instanceof HTTPException) {
HTTPException httpException = (HTTPException) e.getCause();
// Handle authentication failure.
if (httpException != null && httpException.getResponseCode() == HttpURLConnection.HTTP_UNAUTHORIZED) {
returnValue = false;
logMessage("Failed to authenticate with the BWS web service");
logMessage("Exiting %s with value \"%s\"", METHOD_NAME, returnValue);
return returnValue;
}
}
// Log and re-throw exception.
logMessage("Exiting %s with exception \"%s\"", METHOD_NAME, e.getMessage());
throw e;
}
logMessage("Exiting %s with value \"%s\"", METHOD_NAME, returnValue);
return returnValue;
}
/*****************************************************************************************************************
*
* Acquire the SPNEGO token for the BlackBerry Enterprise Service using the currently logged in user's credentials
* and then Base 64 Encode the Token.
*
* @param username The username of the current user.
* @param domain The domain of the user and the BlackBerry Enterprise Server.
* @param kerberosRealm The kerberos realm. It must be uppercase. It is usually equal to the uppercase of the
* domain.
* @param bwsHostname The address of the BlackBerry Enterprise Server hosting BWS.
*
* @return Returns the base 64 encoded SPNEGO token for the currently logged in user.
*
*****************************************************************************************************************
*/
public static String getBase64EncodedSpnegoToken(String username, String domain, String kerberosRealm,
String bwsHostname) throws LoginException, PrivilegedActionException {
String METHOD_NAME = "getBase64EncodedSpnegoToken";
logMessage("Entering %s", METHOD_NAME);
String returnValue = null;
byte[] token = null;
System.setProperty("java.security.krb5.realm", kerberosRealm);
System.setProperty("java.security.krb5.kdc", domain);
final String domainUsername = username + "@" + domain;
final String servicePrincipal = "BASPLUGIN111/" + bwsHostname + "@" + kerberosRealm;
final Subject nullSubject = null;
final CallbackHandler nullCallbackHandler = null;
Configuration config = new Krb5LoginModuleConfiguration();
try {
LoginContext loginContext = new LoginContext(Krb5LoginModuleConfiguration.KERBEROS_CONFIGURATION_NAME,
nullSubject, nullCallbackHandler, config);
loginContext.login();
Subject clientSubject = loginContext.getSubject();
token = (byte[]) Subject.doAs(clientSubject,
new ServiceTicketGenerator(domainUsername, servicePrincipal));
loginContext.logout();
} catch (LoginException e) {
// Log and re-throw exception.
logMessage("Exiting %s with LoginException \"%s\"", METHOD_NAME, e.getMessage());
throw e;
} catch (PrivilegedActionException e) {
// Log and re-throw exception.
logMessage("Exiting %s with PrivilegedActionException \"%s\"", METHOD_NAME, e.getMessage());
throw e;
}
// encode the token using Base64 encoding before returning it
if (token != null) {
returnValue = Base64.encode(token);
}
logMessage("Exiting %s with %s", METHOD_NAME, returnValue == null ? "null" : "a token");
return returnValue;
}
/*****************************************************************************************************************
*
* Creates a string containing the elapsed time since the program started. The execution time will be reset to
* 00:00.000 if the execution time exceeds an hour.
*
* @return Returns the elapsed time from start of program.
*
*****************************************************************************************************************
*/
public static String logTime() {
DateFormat dateFormat = new SimpleDateFormat("mm:ss.SSS");
long timeDifference = System.nanoTime() - startTime;
long dateTime = (timeDifference / NANOSECONDS_IN_A_MILLISECOND);
String time = dateFormat.format(dateTime);
return time;
}
/*****************************************************************************************************************
*
* Prints a log message to stderr.
*
* @param format A string which formats how args will be displayed in the message.
* @param args List of objects to be displayed in the message.
*
*****************************************************************************************************************
*/
public static void logMessage(String format, Object... args) {
// Change output stream if desired
PrintStream logStream = System.err;
logStream.format(logTime() + " " + format + "%n", args);
}
/*****************************************************************************************************************
*
* Logs the calling of an API.
*
*****************************************************************************************************************
*/
public static void logRequest(String bwsApiName) {
logMessage("Calling %s...", bwsApiName);
}
/*****************************************************************************************************************
*
* Logs various information about an API response.
*
*****************************************************************************************************************
*/
public static void logResponse(String bwsApiName, String code, ResponseMetadata metadata) {
logMessage("...%s returned \"%s\"", bwsApiName, code);
if (metadata != null) {
// Converting metadata.getExecutionTime() (which is in nano-seconds) into seconds
logMessage("Execution Time: %.4f seconds", (metadata.getExecutionTime() / NANOSECONDS_IN_A_SECOND));
logMessage("Request UID: %s", metadata.getRequestUid());
}
}
/*****************************************************************************************************************
*
* The main method.
*
* @param args Not used.
* @throws IOException If it fails to create log files.
*
*****************************************************************************************************************
*/
public static void main(String[] args) throws IOException {
// Return codes
final int SUCCESS = 0;
final int FAILURE = 1;
int returnCode = SUCCESS;
// Hostname to use when connecting to web service. Must contain the fully qualified domain name.
// e.g. bwsHostname = "server01.example.net".
String bwsHostname = "<bwsHostname>";
// Port to use when connecting to web service. The same port is used to access the
// webconsole.
String bwsPort = null; // e.g. bwsPort = "38443"
/*
* BWS Host certificate must be installed on the client machine before running this sample code, otherwise a
* SSL/TLS secure channel error will be thrown. For more information, see the BlackBerry Web Services for
* Enterprise Administration For Java Developers Getting Started Guide.
*
* To test authentication populate the methods below with the appropriate credentials and information
*/
// Select which authentication methods you would like to test by setting the variables to true
boolean useBAS = true; // BlackBerry Administration Service
boolean useAD = true; // Active Directory
boolean useSSO = true; // Single Sign On
try {
if (bwsHostname.indexOf('.') < 1) {
throw new Exception("Invalid bwsHostname format. Expected format is \"server01.example.net\"");
}
// bwsPort, if not null, must be a positive integer
if (bwsPort != null) {
int port = Integer.parseInt(bwsPort);
if (port < 1) {
throw new Exception("Invalid bwsPort. Expecting a positive integer string or null");
}
}
if (useBAS) {
returnCode = (demonstrateBlackBerryAdministrationServiceAuthentication(bwsHostname,
bwsPort)) ? SUCCESS : FAILURE;
logMessage("");
}
if (useAD && returnCode == SUCCESS) {
returnCode = (demonstrateActiveDirectoryAuthentication(bwsHostname, bwsPort)) ? SUCCESS : FAILURE;
logMessage("");
}
if (useSSO && returnCode == SUCCESS) {
returnCode = (demonstrateSingleSignOnAuthentication(bwsHostname, bwsPort)) ? SUCCESS : FAILURE;
logMessage("");
}
} catch (Exception e) {
logMessage("Exception: \"%s\"%n", e.getMessage());
e.printStackTrace();
returnCode = FAILURE;
}
System.err.format("Exiting sample.%nPress Enter to exit%n");
System.in.read();
System.exit(returnCode);
}
/*****************************************************************************************************************
* Demonstrates BlackBerry Administration Service Authentication. The required fields are: username, and password.
* Fields denoted by "<value>" must be manually set.
*
* @return Returns true if authenticated successfully, false otherwise.
*****************************************************************************************************************
*/
private static boolean demonstrateBlackBerryAdministrationServiceAuthentication(String bwsHostname,
String bwsPort) {
logMessage("Attempting BlackBerry Administration Service authentication");
// The BlackBerry Administration Service Credentials to use
String username = "<username>"; // e.g. USERNAME = "admin".
String password = "<password>"; // e.g. PASSWORD = "password".
String authenticatorName = "BlackBerry Administration Service";
String activeDirectoryDomain = null; // not needed
CredentialType credentialType = new CredentialType();
credentialType.setPASSWORD(true);
credentialType.setValue("PASSWORD");
return demonstrateBwsSetupAndAuthenticatedCall(bwsHostname, bwsPort, username, password,
activeDirectoryDomain, authenticatorName, credentialType);
}
/*****************************************************************************************************************
* Demonstrates Active Directory Authentication. The required fields are: domain, username, and password. Fields
* denoted by "<value>" must be manually set.
*
* @return Returns true if authenticated successfully, false otherwise.
*****************************************************************************************************************
*/
private static boolean demonstrateActiveDirectoryAuthentication(String bwsHostname, String bwsPort) {
logMessage("Attempting Active Directory authentication");
// The Active Directory Credentials to use
String username = "<username>"; // e.g. username = "admin".
String password = "<password>"; // e.g. password = "password".
String authenticatorName = "Active Directory";
String activeDirectoryDomain = "<domain>"; // e.g. "example.net"
CredentialType credentialType = new CredentialType();
credentialType.setPASSWORD(true);
credentialType.setValue("PASSWORD");
return demonstrateBwsSetupAndAuthenticatedCall(bwsHostname, bwsPort, username, password,
activeDirectoryDomain, authenticatorName, credentialType);
}
/*****************************************************************************************************************
* Demonstrates Single Sign On Authentication. The required fields are: username, domain and realm. The sample
* below automatically acquires the username and domain from the currently logged in user's environment.
*
* @return Returns true if authenticated successfully, false otherwise.
*****************************************************************************************************************
*/
private static boolean demonstrateSingleSignOnAuthentication(String bwsHostname, String bwsPort)
throws WebServiceException {
logMessage("Attempting SSO authentication");
/*
* The 'allowtgtsession' registry key must be set before using SSO, otherwise errors will be thrown. For more
* information, see the BlackBerry Web Services for Enterprise Administration For Java Developers Getting
* Started Guide.
*
*
* For more information about Kerberos and the Java GSSAPI see:
* http://docs.oracle.com/javase/6/docs/technotes/guides/security/jgss/single-signon.html and
* http://web.mit.edu/kerberos/krb5-latest/doc
*/
// The SSO Credentials to use. Automatically acquires the currently
// logged in user and their Kerberos TGT (Ticket Granting Ticket)
String username = System.getProperty("user.name");
String password = null; // is populated by getBase64EncodedSPNEGOToken() below
String authenticatorName = "Active Directory";
String activeDirectoryDomain = System.getenv("USERDNSDOMAIN"); // e.g. "example.net"
CredentialType credentialType = new CredentialType();
credentialType.setSSO(true);
credentialType.setValue("SSO");
String kerberosRealm = bwsHostname.substring(bwsHostname.indexOf('.') + 1).toUpperCase();
try {
try {
password = getBase64EncodedSpnegoToken(username, activeDirectoryDomain, kerberosRealm, bwsHostname);
} catch (Exception e) {
logMessage("Exception: \"%s\"", e.getMessage());
}
if (password == null) {
logMessage("Failed to retrieve SPNEGO Token for SSO.");
return false;
}
} catch (WebServiceException e) {
logMessage("Exception: \"%s\"", e.getMessage());
throw e;
}
return demonstrateBwsSetupAndAuthenticatedCall(bwsHostname, bwsPort, username, password,
activeDirectoryDomain, authenticatorName, credentialType);
}
/*****************************************************************************************************************
* Tests if the passed in settings successfully authenticate against BWS.
*
* @return Returns true if authenticated successfully, false otherwise.
*****************************************************************************************************************
*/
private static boolean demonstrateBwsSetupAndAuthenticatedCall(String bwsHostname, String bwsPort,
String username, String password, String activeDirectoryDomain, String authenticatorName,
CredentialType credentialType) throws WebServiceException {
boolean returnCode = false;
logMessage("Initializing web services...");
if (setup(bwsHostname, bwsPort, username, password, authenticatorName, credentialType,
activeDirectoryDomain)) {
/*
* Demonstrate authenticated call to _bws.echo() API.
*/
logMessage("Attempting authenticated BWS call to echo()...");
if (echo()) {
logMessage("Authenticated call succeeded!");
returnCode = true;
} else {
logMessage("Authenticated call failed!");
}
} else {
logMessage("Error: setup() failed");
}
return returnCode;
}
/*****************************************************************************************************************
*
* Initialize the BWS and BWSUtil services.
*
* @return Returns true when the setup is successful, and false otherwise.
*
*****************************************************************************************************************
*/
private static boolean setup(String hostname, String bwsPort, String username, String password,
String authenticatorName, CredentialType credentialType, String domain) {
final String METHOD_NAME = "setup()";
logMessage("Entering %s", METHOD_NAME);
boolean returnValue = false;
REQUEST_METADATA.setClientVersion(CLIENT_VERSION);
REQUEST_METADATA.setLocale(LOCALE);
REQUEST_METADATA.setOrganizationUid(ORG_UID);
URL bwsServiceUrl = null;
URL bwsUtilServiceUrl = null;
try {
// These are the URLs that point to the web services used for all calls.
// e.g. with no port:
// https://server01.example.net/enterprise/admin/ws
// e.g. with port:
// https://server01.example.net:38443/enterprise/admin/ws
String port = "";
if (bwsPort != null) {
port = ":" + bwsPort;
}
bwsServiceUrl = new URL("https://" + hostname + port + "/enterprise/admin/ws");
bwsUtilServiceUrl = new URL("https://" + hostname + port + "/enterprise/admin/util/ws");
} catch (MalformedURLException e) {
logMessage("Cannot initialize web service URLs");
logMessage("Exiting %s with value \"%s\"", METHOD_NAME, returnValue);
return returnValue;
}
// Initialize the BWS web service stubs that will be used for all calls.
logMessage("Initializing BWS web service stub");
QName serviceBWS = new QName("http://ws.rim.com/enterprise/admin", "BWSService");
QName portBWS = new QName("http://ws.rim.com/enterprise/admin", "BWS");
_bwsService = new BWSService(null, serviceBWS);
_bwsService.addPort(portBWS, "http://schemas.xmlsoap.org/soap/", bwsServiceUrl.toString());
_bws = _bwsService.getPort(portBWS, BWS.class);
logMessage("BWS web service stub initialized");
logMessage("Initializing BWSUtil web service stub");
QName serviceUtil = new QName("http://ws.rim.com/enterprise/admin", "BWSUtilService");
QName portUtil = new QName("http://ws.rim.com/enterprise/admin", "BWSUtil");
_bwsUtilService = new BWSUtilService(null, serviceUtil);
_bwsUtilService.addPort(portUtil, "http://schemas.xmlsoap.org/soap/", bwsUtilServiceUrl.toString());
_bwsUtil = _bwsUtilService.getPort(portUtil, BWSUtil.class);
logMessage("BWSUtil web service stub initialized");
// Set the connection timeout to 60 seconds.
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(60000);
httpClientPolicy.setAllowChunking(false);
httpClientPolicy.setReceiveTimeout(60000);
Client client = ClientProxy.getClient(_bws);
HTTPConduit http = (HTTPConduit) client.getConduit();
http.setClient(httpClientPolicy);
client = ClientProxy.getClient(_bwsUtil);
http = (HTTPConduit) client.getConduit();
http.setClient(httpClientPolicy);
Authenticator authenticator = getAuthenticator(authenticatorName);
if (authenticator != null) {
String encodedUsername = getEncodedUserName(username, authenticator, credentialType, domain);
if (encodedUsername != null && !encodedUsername.isEmpty()) {
/*
* Set the HTTP basic authentication on the BWS service. BWSUtilService is a utility web service that
* does not require authentication.
*/
BindingProvider bp = (BindingProvider) _bws;
bp.getRequestContext().put(BindingProvider.USERNAME_PROPERTY, encodedUsername);
bp.getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, password);
returnValue = true;
} else {
logMessage("\"encodedUsername\" is null or empty");
}
} else {
logMessage("\"authenticator\" is null");
}
logMessage("Exiting %s with value \"%s\"", METHOD_NAME, returnValue);
return returnValue;
}
}