/*******************************************************************************
*
* Copyright 2011-2014 Spiffy UI Team
*
* 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.
*
******************************************************************************/
package org.spiffyui.client.rest;
import org.spiffyui.client.MessageUtil;
import org.spiffyui.client.i18n.SpiffyUIStrings;
import org.spiffyui.client.login.LoginPanel;
import org.spiffyui.client.login.LoginStringHelper;
import com.google.gwt.core.client.GWT;
import com.google.gwt.http.client.Response;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.Window;
/**
* <p>
* This is the default class for handling authentication during REST calls.
* </p>
*
* <p>
* The default authentication handler follows the Novell standards for REST
* authentication. This class implements that handler. You can override this
* class and create your own authentication handler to satisfy any authentication
* scheme your application may require.
* </p>
*/
public class AuthUtil implements org.spiffyui.client.rest.v2.RESTAuthProvider
{
private static final SpiffyUIStrings STRINGS = (SpiffyUIStrings) GWT.create(SpiffyUIStrings.class);
/**
* The string helper for providing strings to the login dialog
*/
protected static final LoginStringHelper HELPER = new LoginStringHelper();
/**
* Create a new AuthUtil
*/
public AuthUtil()
{
}
/**
* <p>
* Show the login dialog.
* </p><p>
* <p>
* This method is called when a REST call results in a 401 which requires login.
* The default implementation of this method shows a login dialog, but it could
* redirect to another page or simply show an error message.
* </p>
*
* @param callback the callback for the original REST call
* @param tokenServerUrl
* the URL for the authentication server
* @param response the server response that came with this 401
* @param exception the RESTException representation of the JSON response from the server if available
*/
public void showLogin(RESTCallback callback, String tokenServerUrl, Response response, RESTException exception)
{
/*
We don't need to implement this method here because we don't use any of the extend objects,
but we want to implement this interface since this is a popular class to subclass and we
want to give those child classes access to the extended objects.
*/
String code = null;
if (exception != null) {
code = exception.getSubcode();
}
if (tokenServerUrl == null) {
throw new IllegalArgumentException(STRINGS.invalidAuthHeader(response.getHeader("WWW-Authenticate")));
}
showLogin(callback, tokenServerUrl, code);
}
/**
* Show the login dialog.
*
* @param callback the callback for the original REST call
* @param tokenServerUrl
* the URL for the authentication server
* @param code the error code
*/
public void showLogin(RESTCallback callback, String tokenServerUrl, String code)
{
if (RESTility.hasUserLoggedIn()) {
LoginPanel.showLoginPanel(getStringHelper(), STRINGS.renew(), callback, tokenServerUrl, code, true, RESTility.getUsername());
} else {
LoginPanel.showLoginPanel(getStringHelper(), STRINGS.loginTitle(), callback, tokenServerUrl, code, false, null);
}
}
/**
* Provide the LoginStringHelper class for use with the login panel.
*
* @return the LoginStringHelper
*/
protected LoginStringHelper getStringHelper()
{
return HELPER;
}
/**
* Once the login is completed we need to perform the original REST request
* with the new user token.
*
* @param callBackKey
* the callback object of the original REST call
*/
public void finishRESTCall(Object callBackKey)
{
RESTility.finishRESTCalls();
}
/**
* Logout the current user. This clears all local variables, removes the
* session cookie, and issues the logout request to the server.
*
* @param callback the REST callback to indicate the call is completed
*/
public void logout(final RESTObjectCallBack<String> callback)
{
JSONObject credentials = new JSONObject();
credentials.put(USER_TOKEN, new JSONString(RESTility.getUserToken()));
credentials.put(AUTH_URL_TOKEN, new JSONString(RESTility.getTokenServerUrl()));
credentials.put(AUTH_LOGOUT_URL_TOKEN, new JSONString(RESTility.getTokenServerLogoutUrl()));
RESTility.callREST(getServletContext() + "/auth", credentials.toString(), RESTility.DELETE,
new RESTCallback() {
@Override
public void onSuccess(JSONValue val)
{
RESTility.doLocalLogout();
callback.success("");
}
@Override
public void onError(int statusCode, String errorResponse)
{
RESTility.doLocalLogout();
callback.error(errorResponse);
}
@Override
public void onError(RESTException e)
{
handleLogoutError(callback, e);
}
});
}
private void handleLogoutError(RESTObjectCallBack<String> callback, RESTException e)
{
RESTility.doLocalLogout();
if (e.getSubcode().equals(GONE)) {
/*
* This means the token has timed out. That makes the logout
* extraneous, but we want to handle it like a normal logout
* in the UI.
*/
callback.success("");
} else {
callback.error(e);
}
}
/**
* Performs login for the specified user credentials.
*
* @param username the username
* @param password the password
* @param authUrl the URL of the authentication server
* @param callback the REST callback for the original REST call
*/
public void login(final String username, final String password, final String authUrl,
final RESTObjectCallBack<String> callback)
{
JSONObject credentials = new JSONObject();
credentials.put(USERNAME_TOKEN, new JSONString(username));
credentials.put(PASSWORD_TOKEN, new JSONString(password));
credentials.put(AUTH_URL_TOKEN, new JSONString(authUrl));
if (RESTility.getTokenServerLogoutUrl() != null) {
credentials.put(AUTH_LOGOUT_URL_TOKEN, new JSONString(RESTility.getTokenServerLogoutUrl()));
} else {
credentials.put(AUTH_LOGOUT_URL_TOKEN, new JSONString(authUrl));
}
RESTility.callREST(getServletContext() + "/auth", credentials.toString(), RESTility.POST,
new RESTCallback() {
@Override
public void onSuccess(JSONValue val)
{
doLogin(callback, val, username, authUrl);
}
@Override
public void onError(int statusCode, String errorResponse)
{
callback.error(errorResponse);
}
@Override
public void onError(RESTException e)
{
callback.error(e);
}
}, true, null);
}
private void doLogin(final RESTObjectCallBack<String> callback, JSONValue val, String username, String authUrl)
{
RESTility.setTokenServerURL(authUrl);
RESTility.setUsername(username);
if (val == null) {
callback.error(STRINGS.loginDataError(""));
MessageUtil.showError(STRINGS.loginDataError(""));
return;
}
JSONObject o = val.isObject();
if (o == null) {
callback.error(STRINGS.loginDataError(val.toString()));
MessageUtil.showError(STRINGS.loginDataError(val.toString()));
return;
}
RESTility.setUserToken(o.get("Token").isString().stringValue());
RESTility.fireLoginSuccess();
callback.success(o.get("Token").isString().stringValue());
}
/**
* Gets the servlet context from the current URL. The context is assumed
* to be the string after the first single forward slash and before the next
* forward slash.
*
* @return the current server servlet context
*/
public String getServletContext()
{
String url = Window.Location.getHref();
int index = url.indexOf("/", url.indexOf("//") + 2);
//Fix array out of bound error in GWT hosted mode by checking if index2 is -1
int index2 = url.indexOf("/", index + 2);
if (index2 != -1) {
String context = url.substring(index + 1, index2);
if (context.length() > 0) {
return "/" + context;
} else {
return "";
}
} else {
return "";
}
}
}