diff --git a/Gatekeeper/WebRoot/META-INF/MANIFEST.MF b/Gatekeeper/WebRoot/META-INF/MANIFEST.MF new file mode 100644 index 00000000..254272e1 --- /dev/null +++ b/Gatekeeper/WebRoot/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/Gatekeeper/WebRoot/WEB-INF/conf/gatekeeper.properties b/Gatekeeper/WebRoot/WEB-INF/conf/gatekeeper.properties new file mode 100644 index 00000000..856dc168 --- /dev/null +++ b/Gatekeeper/WebRoot/WEB-INF/conf/gatekeeper.properties @@ -0,0 +1,7 @@ +web.service.version = GateKeeper-0.1 +token.name = auth-token +token.ttl = 28800000 +token.authgroup = authenticated +token.publicuser = public +ldap.keystore = keystore.jks +token.privatekey = private-key.ser diff --git a/Gatekeeper/WebRoot/WEB-INF/conf/keystore.jks b/Gatekeeper/WebRoot/WEB-INF/conf/keystore.jks new file mode 100644 index 00000000..452d963e Binary files /dev/null and b/Gatekeeper/WebRoot/WEB-INF/conf/keystore.jks differ diff --git a/Gatekeeper/WebRoot/WEB-INF/conf/log4j.properties b/Gatekeeper/WebRoot/WEB-INF/conf/log4j.properties new file mode 100644 index 00000000..4db65773 --- /dev/null +++ b/Gatekeeper/WebRoot/WEB-INF/conf/log4j.properties @@ -0,0 +1,59 @@ +# /** +# * '$rcsfile: log4j.properties,v $' +# * copyright: 2002 regents of the university of california and the +# * national center for ecological analysis and synthesis +# * '$author: brooke $' +# * '$date: 2003/06/24 00:58:49 $' +# * '$revision: 1.1 $' +# * +# * this program is free software; you can redistribute it and/or modify +# * it under the terms of the gnu general public license as published by +# * the free software foundation; either version 2 of the license, or +# * (at your option) any later version. +# * +# * this program is distributed in the hope that it will be useful, +# * but without any warranty; without even the implied warranty of +# * merchantability or fitness for a particular purpose. see the +# * gnu general public license for more details. +# * +# * you should have received a copy of the gnu general public license +# * along with this program; if not, write to the free software +# * foundation, inc., 59 temple place, suite 330, boston, ma 02111-1307 usa +# */ +# +# +################################################################################ +################################################################################ +# +# for conversion/formatting characters, see: +# +# http://logging.apache.org/log4j/docs/api/org/apache/log4j/PatternLayout.html +# +################################################################################ +################################################################################ + +# set the log level to WARN and the log should be printed to stdout. +log4j.rootLogger=WARN, stdout +log4j.threshold=FATAL, ERROR, WARN, INFO + + +### LOGGING TO CONSOLE ######################################################### +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout + +# define the pattern to be used in the logs... +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} Gatekeeper: [%p] [%c]: %m %n + +# %p -> priority level of the event - (e.g. WARN) +# %m -> message to be printed +# %c -> category name ... in this case name of the class +# %d -> Used to output the date of the logging event. example, %d{HH:mm:ss,SSS} or %d{dd MMM yyyy HH:mm:ss,SSS}. Default format is ISO8601 format +# %M -> print the method name where the event was generated ... can be extremely slow. +# %L -> print the line number of the event generated ... can be extremely slow. +# %t -> Used to output the name of the thread that generated the log event +# %n -> carriage return + +################################################################################ +# EXAMPLE: Print only messages of level WARN or above in the package com.foo: +# log4j.logger.com.foo=WARN +log4j.logger.edu.lternet.pasta.gatekeeper=INFO diff --git a/Gatekeeper/WebRoot/WEB-INF/conf/private-key.ser b/Gatekeeper/WebRoot/WEB-INF/conf/private-key.ser new file mode 100644 index 00000000..d2bdd423 Binary files /dev/null and b/Gatekeeper/WebRoot/WEB-INF/conf/private-key.ser differ diff --git a/Gatekeeper/WebRoot/WEB-INF/web.xml b/Gatekeeper/WebRoot/WEB-INF/web.xml new file mode 100644 index 00000000..658e5ad0 --- /dev/null +++ b/Gatekeeper/WebRoot/WEB-INF/web.xml @@ -0,0 +1,106 @@ + + + + CONFIG_DIR + WEB-INF/conf + + + edu.lternet.pasta.gatekeeper.ConfigurationListener + + + GatekeeperEventManagerProxy + org.eclipse.jetty.servlets.ProxyServlet$Transparent + 1 + + Prefix + /eventmanager + + + ProxyTo + http://event.lternet.edu:8080/eventmanager + + + + GatekeeperDataPackageManagerProxy + org.eclipse.jetty.servlets.ProxyServlet$Transparent + 1 + + Prefix + /package + + + ProxyTo + http://package.lternet.edu:8080/package + + + + GatekeeperAuditManagerProxy + org.eclipse.jetty.servlets.ProxyServlet$Transparent + 1 + + Prefix + /audit + + + ProxyTo + http://audit.lternet.edu:8080/audit + + + + GatekeeperTester + org.eclipse.jetty.servlets.ProxyServlet$Transparent + 1 + + Prefix + /test + + + ProxyTo + http://localhost:8080/gatekeepertester + + + + api-docs + org.eclipse.jetty.servlet.DefaultServlet + + + demo + org.eclipse.jetty.servlet.DefaultServlet + + + GatekeeperFilter + edu.lternet.pasta.gatekeeper.GatekeeperFilter + + + GatekeeperFilter + GatekeeperEventManagerProxy + GatekeeperDataPackageManagerProxy + GatekeeperAuditManagerProxy + GatekeeperTester + + + GatekeeperEventManagerProxy + /eventmanager/* + + + GatekeeperDataPackageManagerProxy + /package/* + + + GatekeeperAuditManagerProxy + /audit/* + + + GatekeeperTester + /test/* + + + api-docs + /docs/* + + + demo + /demo/* + + diff --git a/Gatekeeper/WebRoot/demo/demo.html b/Gatekeeper/WebRoot/demo/demo.html new file mode 100644 index 00000000..11076b05 --- /dev/null +++ b/Gatekeeper/WebRoot/demo/demo.html @@ -0,0 +1,186 @@ + + + + Gatekeeper/Authentication Acceptance Tests + + + + + + + + + + +

+ Gatekeeper/Authentication Acceptance Tests +

+
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Web Service Requests +
+ +
+ Request Description +
+ +
+ Base URL: https://pasta.lternet.edu +
+
+ Relative URL +
+ +
+ HTTP verb +
+ +
+ User +
+ +
+ Password +
+ +
+ Request Content-Type +
+ +
+ Request Entity +
+ +
+ + +
+
+ +
+ +
+
+ + + + + + + +
+ Response +
+
+ +
+
+
+
+
+
+
+ + + diff --git a/Gatekeeper/WebRoot/demo/style.css b/Gatekeeper/WebRoot/demo/style.css new file mode 100644 index 00000000..04a61d92 --- /dev/null +++ b/Gatekeeper/WebRoot/demo/style.css @@ -0,0 +1,69 @@ +.demo { + width: 580px; + float: left; + padding-top: 0px; + padding-left: 0px; + padding-right: 16px; + padding-bottom: 0px; +} + +.mainResult { + width: 580px; + float: left; + padding-top: 0px; + padding-left: 8px; + padding-right: 0px; + padding-bottom: 0px; +} + +.webServiceDescription { + width: 580px; + height: 80px; + float: left; + overflow: auto; + padding: 4px; + margin: 2px; + background: #EEEEEE; + border-style: solid; + border-color: #CFCFCF; + border-width: 1px; + font-family: Arial, sans-serif; + font-size: 12px; +} + +.webServiceResult { + width: 580px; + height: 400px; + float: left; + overflow: auto; + padding: 4px; + margin: 2px; + background: #CCE6FF; + border-style: solid; + border-color: #CFCFCF; + border-width: 1px; +} + +.formTextField { + font-family: Arial, sans-serif; + font-size: 12px; + width: 580px; +} + +.formLabelField { + font-family: Arial, sans-serif; + font-size: 12px; + width: 580px; + background: #EEEEEE; +} + +.formTextArea { + font-family: Arial, sans-serif; + font-size: 12px; + width: 580px; +} + +.content { + font-family: Arial, sans-serif; + font-size: 12px; +} diff --git a/Gatekeeper/WebRoot/demo/util.js b/Gatekeeper/WebRoot/demo/util.js new file mode 100644 index 00000000..9508f12f --- /dev/null +++ b/Gatekeeper/WebRoot/demo/util.js @@ -0,0 +1,100 @@ + +function createCookie(name,value,days) { + if (days) { + var date = new Date(); + date.setTime(date.getTime()+(days*24*60*60*1000)); + var expires = "; expires="+date.toGMTString(); + } + else var expires = ""; + document.cookie = name+"="+value+expires+"; path=/"; +} + +function alterCookie(name) { + createCookie(name, "THISISBROKEN"); +} + +function eraseCookie(name) { + createCookie(name, "", -1); +} + +function xmlToString(xmlObject) { + if (window.ActiveXObject) + return xmlObject.xml; + else + return (new XMLSerializer()).serializeToString(xmlObject); +} + +function getResponseEntityAsString(xmlhttp) { + if (xmlhttp.responseText && xmlhttp.responseText != "") + return xmlhttp.responseText; + if (xmlhttp.responseXml) + return xmlToString(xmlhttp.responseXML); + return ""; +} + +function makeBasicAuth(user, password) { + var token = user + ':' + password; + var encodedToken = Base64.encode(token); + return "Basic " + encodedToken; +} + +function clearRequest() { + document.requestForm.description.value = ""; + document.requestForm.relativeUrl.value = ""; + document.requestForm.httpVerb.value = ""; + document.requestForm.user.value = ""; + document.requestForm.password.value = ""; + document.requestForm.contentType.value = ""; + document.requestForm.entity.value = ""; +} + +function clearResponse() { + document.getElementById("response").value = ""; +} + +function clearRequestAndResponse() { + clearRequest(); + clearResponse(); +} + +function sendRequest() { + + // Validate input first + if (document.requestForm.httpVerb.value == "") { + alert("HTTP verb can't be empty."); + return; + } + + // Initializing response panel + document.getElementById("response").value = "Waiting..."; + + // Building the request + xmlhttp = new XMLHttpRequest(); + xmlhttp.open(document.requestForm.httpVerb.value, + encodeURI(document.requestForm.relativeUrl.value), false); + + // Adding user and password to the request + if ((document.requestForm.user.value != "") && + (document.requestForm.password.value != "")) { + auth = makeBasicAuth(document.requestForm.user.value, + document.requestForm.password.value); + xmlhttp.setRequestHeader('Authorization', auth); + } + + // Adding the entity and content-type to the request, if necessary + if (document.requestForm.entity.value == "") { + xmlhttp.send(); + } else { + contentType = document.requestForm.contentType.value; + xmlhttp.setRequestHeader("Content-Type", contentType); + xmlhttp.send(document.requestForm.entity.value); + } + + // Building the response to be written to the panel + responseText = 'Status: ' + xmlhttp.status + ' ' + xmlhttp.statusText + '\n'; + responseText += xmlhttp.getAllResponseHeaders() + '\n'; + responseText += getResponseEntityAsString(xmlhttp); + + // Adding the response to the panel + document.getElementById("response").value = responseText; +} diff --git a/Gatekeeper/WebRoot/demo/web-service-requests.js b/Gatekeeper/WebRoot/demo/web-service-requests.js new file mode 100644 index 00000000..d0200ba1 --- /dev/null +++ b/Gatekeeper/WebRoot/demo/web-service-requests.js @@ -0,0 +1,59 @@ +var Requests = { + + _relativeUrl : "/test/", + + _setUserAndPassword : function(requestForm) { + requestForm.user.value = "uid=ucarroll,o=LTER,dc=ecoinformatics,dc=org"; + requestForm.password.value = "S@ltL@ke"; + }, + + _clearUserAndPassword : function(requestForm) { + requestForm.user.value = ""; + requestForm.password.value = ""; + }, + + _clearCookie : function(cookie) { + var cookies = document.cookie.split(";") + for (var i = 0; i < cookies.length; i++) + eraseCookie(cookies[i].split("=")[0]); + }, + + initializeReadBasic : function(requestForm, cookie) { + requestForm.description.value = "Perform a GET using basic authentication."; + requestForm.relativeUrl.value = this._relativeUrl; + requestForm.httpVerb.value = "GET"; + requestForm.contentType.value = "N/A"; + requestForm.entity.value = ""; + this._setUserAndPassword(requestForm); + this._clearCookie(cookie); + }, + + initializeReadToken : function(requestForm) { + requestForm.description.value = "Perform a GET using token authentication. Alternately perform a GET using an expired token (wait 60 seconds)."; + requestForm.relativeUrl.value = this._relativeUrl; + requestForm.httpVerb.value = "GET"; + requestForm.contentType.value = "N/A"; + requestForm.entity.value = ""; + this._clearUserAndPassword(requestForm); + }, + + initializeReadMalformed : function(requestForm) { + requestForm.description.value = "Perform a GET using a malformed token for authentication."; + requestForm.relativeUrl.value = this._relativeUrl; + requestForm.httpVerb.value = "GET"; + requestForm.contentType.value = "N/A"; + requestForm.entity.value = ""; + this._clearUserAndPassword(requestForm); + }, + + initializeReadPublic : function(requestForm, cookie) { + requestForm.description.value = "Perform a GET using public authentication."; + requestForm.relativeUrl.value = this._relativeUrl; + requestForm.httpVerb.value = "GET"; + requestForm.contentType.value = "N/A"; + requestForm.entity.value = ""; + this._clearUserAndPassword(requestForm); + this._clearCookie(cookie); + } + +} diff --git a/Gatekeeper/WebRoot/demo/webtoolkit.base64.js b/Gatekeeper/WebRoot/demo/webtoolkit.base64.js new file mode 100644 index 00000000..5fa3c1ed --- /dev/null +++ b/Gatekeeper/WebRoot/demo/webtoolkit.base64.js @@ -0,0 +1,142 @@ +/** +* +* Base64 encode / decode +* http://www.webtoolkit.info/ +* +**/ + +var Base64 = { + + // private property + _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + + // public method for encoding + encode : function (input) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + + input = Base64._utf8_encode(input); + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); + + } + + return output; + }, + + // public method for decoding + decode : function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = this._keyStr.indexOf(input.charAt(i++)); + enc2 = this._keyStr.indexOf(input.charAt(i++)); + enc3 = this._keyStr.indexOf(input.charAt(i++)); + enc4 = this._keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + } + + output = Base64._utf8_decode(output); + + return output; + + }, + + // private method for UTF-8 encoding + _utf8_encode : function (string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + + var c = string.charCodeAt(n); + + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + + return utftext; + }, + + // private method for UTF-8 decoding + _utf8_decode : function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + + while ( i < utftext.length ) { + + c = utftext.charCodeAt(i); + + if (c < 128) { + string += String.fromCharCode(c); + i++; + } + else if((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + + } + + return string; + } + +} diff --git a/Gatekeeper/WebRoot/docs/api.html b/Gatekeeper/WebRoot/docs/api.html new file mode 100644 index 00000000..89df7e1d --- /dev/null +++ b/Gatekeeper/WebRoot/docs/api.html @@ -0,0 +1,51 @@ + + + + + Gatekeeper - PASTA + + +

+ Gatekeeper web service API +

+
+

Base URL - https://pasta.lternet.edu

+

+ The Gatekeeper web service handles all authentication from incoming requests. +

+

The following are the Use Cases for the Gatekeeper:

+
  • + If the user submits only BASIC authentication credentials, a token will be + generated and returned upon completion of the requested query. +
  • +
  • + If the user submits a token, the token will be used provided it does not + exceed the time to live. In that event, a ServletException is thrown. +
  • +
  • + If no credentials or tokens are submitted, a token for special user public + will be created and the remainder of the query will be done as public. The + response will return a public token. +
  • +

    There is no explicit API for interacting with the Gatekeeper as a service. + The requirement for all services is using a basic Authorization header. + Once a cookie is received from a request, using that cookie instead of the + Authorization header will result in being able to interact with all pasta + services without transferring user authentication credentials until the cookie + expires or the user determines it necessary to authenticate using alternative + credentials.

    +

    The following services can be used in conjunction with the Gatekeeper: +

  • DataPackageManager @ http://pasta.lternet.edu/package/
  • +
  • GatekeeperTester @ http://pasta.lternet.edu/test
  • + +

    + + + diff --git a/Gatekeeper/WebRoot/docs/tutorial.html b/Gatekeeper/WebRoot/docs/tutorial.html new file mode 100644 index 00000000..711186a5 --- /dev/null +++ b/Gatekeeper/WebRoot/docs/tutorial.html @@ -0,0 +1,84 @@ + + + + Tutorial - GatekeeperTester - PASTA + + +

    + GatekeeperTester + web service tutorial +

    +
    +

    Examples

    + + +

    The command line utility curl will be used to + illustrate the following examples.

    + +
    +

    This service exists for testing the Gatekeeper

    + +

    Intial Authentication Command

    + +

    curl -c cookiejar.txt --user uid=ucarroll,o=LTER,dc=ecoinformatics,dc=org:mouse -G + https://pasta.lternet.edu/test/ +

    + +

    Description

    + +

    This command requests that the Gatekeeper create a new + token to be returned as a cookie when the request completes. This will then be + written to the cookie store cookiejar.txt for future use. The user is + encouraged to use uid=ucarroll,o=LTER,dc=ecoinformatics,dc=org + and corresponding password for testing. Should an invalid username or password + be used, an appropriate response will be generated indicating such.

    + +

    The response from the Gatekeeper depends on the nature of the submitted + request, but the Set-Cookie: header should show a Base 64 encoded string has + been returned.

    + +
    +HTTP/1.1 200 OK
    +Date: Thu, 22 Dec 2011 19:07:50 GMT
    +Set-Cookie: auth-token="sz46tDcFxqLby2TtlBARREdqGFSSRFbjSHPvMw0hgXLsG2uGlDWrOzjf/zM7Yd7g4n8pK5qKzohvP9UdYqf/xyx/RBAUU1QYwmUXTA5NnUZ5qHjYCtx3Y+DgwyNsQPoz6dQqR92BWWsWb39BilwfaYGtyAtiztJ0ZK4mx3c94VY9HTXmhfbuMzAErLo437zvX4IZDSHWMWNXYMuxC3eQ+A==";Version=1;Expires=Fri, 12-Dec-2053 14:20:40 GMT;Max-Age=1324581170
    +Expires: Thu, 01 Jan 1970 00:00:00 GMT
    +Server: Apache-Coyote/1.1
    +...
    +
    + +

    Omission of the --user <username>:<password> will + result in public authentication. +

    + +
    + +

    Token Authentication Command

    + +

    curl -b cookiejar.txt -G https://pasta.lternet.edu/test/ +

    + +

    Description

    + +

    This command requests that the Gatekeeper use a previously created + token for the request. This token will be retreived from the + cookiejar.txt and submitted appropriately. + +

    The response from the Gatekeeper depends on the nature of the submitted + request and should not include any headers or content indicating anything + other than normal operation. If the token time to live has expired, a response + will be generated to indicate so.

    + +
    +HTTP/1.1 200 OK
    +Date: Thu, 22 Dec 2011 19:07:50 GMT
    +Server: Apache-Coyote/1.1
    +...
    +
    + +

    + +
    +
    +
    + + diff --git a/Gatekeeper/WebRoot/index.jsp b/Gatekeeper/WebRoot/index.jsp new file mode 100644 index 00000000..08660428 --- /dev/null +++ b/Gatekeeper/WebRoot/index.jsp @@ -0,0 +1,26 @@ +<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%> +<% +String path = request.getContextPath(); +String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/"; +%> + + + + + + + My JSP 'index.jsp' starting page + + + + + + + + + + This is my JSP page.
    + + diff --git a/Gatekeeper/build.properties b/Gatekeeper/build.properties new file mode 100644 index 00000000..3de5a37c --- /dev/null +++ b/Gatekeeper/build.properties @@ -0,0 +1,3 @@ +# Ant properties +tomcat.home=/home/pasta/local/jetty +tomcat.lib=/home/pasta/local/jetty/lib diff --git a/Gatekeeper/build.xml b/Gatekeeper/build.xml new file mode 100644 index 00000000..f2231b1a --- /dev/null +++ b/Gatekeeper/build.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Gatekeeper/documents/gatekeeper.eap b/Gatekeeper/documents/gatekeeper.eap new file mode 100644 index 00000000..b20246c1 Binary files /dev/null and b/Gatekeeper/documents/gatekeeper.eap differ diff --git a/Gatekeeper/src/edu/lternet/pasta/gatekeeper/ConfigurationListener.java b/Gatekeeper/src/edu/lternet/pasta/gatekeeper/ConfigurationListener.java new file mode 100644 index 00000000..dbee8084 --- /dev/null +++ b/Gatekeeper/src/edu/lternet/pasta/gatekeeper/ConfigurationListener.java @@ -0,0 +1,305 @@ +/* + * + * $Date$ $Author$ $Revision$ + * + * Copyright 2010 the University of New Mexico. + * + * This work was supported by National Science Foundation Cooperative Agreements + * #DEB-0832652 and #DEB-0936498. + * + * 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 edu.lternet.pasta.gatekeeper; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; + +import javax.crypto.SecretKey; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; + +import edu.lternet.pasta.common.FileUtility; +import edu.lternet.pasta.common.UserErrorException; +import edu.lternet.pasta.common.security.auth.SymmetricEncrypter; + +/** + * The ConfigurationListener class initializes the LTER restful web application. + * The initialization code executes when the web application context starts up. + * This class is a subclass of ServletContextListener. + */ +public class ConfigurationListener implements ServletContextListener +{ + + private static final Logger logger = + Logger.getLogger(ConfigurationListener.class); + + /* Name of Gatekeeper's properties file. */ + public static final String PROPERTIES = "gatekeeper.properties"; + + public static final String TOKEN_NAME = "token.name"; + public static final String TOKEN_TTL = "token.ttl"; + public static final String AUTH_GROUP = "token.authgroup"; + public static final String PUBLIC_USER = "token.publicuser"; + public static final String LDAP_KEY_STORE = "ldap.keystore"; + public static final String PRIVATE_KEY = "token.privatekey"; + public static final String WEB_SERVICE_VERSION = "web.service.version"; + + private static String tokenName = null; + private static long tokenTtl = new Long(-1); + private static String authGroup = null; + private static String publicUser = null; + private static File ldapKeyStore = null; + private static SecretKey privateKey = null; + private static String webServiceVersion = null; + + private static File configDir; + private static File propertiesFile; + + /** + * Getter for the configDir. + * + * @return the directory as a File. + */ + public static File getConfigDir() { + + if (configDir == null) { + File f = new File(System.getProperty("user.dir"), "WebRoot"); + f = new File(f, "WEB-INF"); + return new File(f, "conf"); + } + return configDir; + } + + /** + * Getter for the tokenName class field. + * + * @return the tokenName class field. + */ + public static String getTokenName() { + return tokenName; + } + + /** + * Getter for the tokenTtl class field. + * + * @return the tokenTtl class field. + */ + public static long getTokenTtl() { + return tokenTtl; + } + + /** + * Getter for the webServiceVersion class field. + * + * @return the webServiceVersion class field. + */ + public static String getWebServiceVersion() { + return webServiceVersion; + } + + /** + * Getter for the authGroup class field. + * + * @return the authGroup class field. + */ + public static String getAuthGroup() { + return authGroup; + } + + /** + * Getter for the publicUser class field. + * + * @return the publicUser class field. + */ + public static String getPublicUser() { + return publicUser; + } + + /** + * Getter for the ldapKeyStore class field. + * + * @return the ldapKeyStore class field. + */ + public static File getLdapKeyStore() { + return ldapKeyStore; + } + + /** + * Getter for the privateKey class field. + * + * @return the privateKey class field. + */ + public static SecretKey getPrivateKey() { + return privateKey; + } + + /** + * This method can be used to execute code when the web application shuts + * down. + * + * @param servletContextEvent + * The ServletContextEvent object. + */ + public void contextDestroyed(ServletContextEvent servletContextEvent) { + + } + + /** + * Run initialization code when at web application start-up. + * + * @param servletContextEvent + * The ServletContextEvent object. + */ + public void contextInitialized(ServletContextEvent servletContextEvent) { + + ServletContext context = servletContextEvent.getServletContext(); + String CONFIG_DIR = context.getInitParameter("CONFIG_DIR"); + String realPath = context.getRealPath(CONFIG_DIR); + try { + setConfigDir(realPath); + initialize(); + } + catch (RuntimeException e) { + logger.fatal("Initialization of Gatekeeper failed.", e); + throw e; + } + } + + /** + * Initialize all Properties files and prepare retrievable values. + * + * @param dir + * The directory containing properties files. + */ + public void initialize(String dir) { + + // necessary for JUnit tests + configDir = new File(dir); + initialize(); + } + + /** + * Initialize all Properties files and prepare retrievable values with + * default configuration directory. + */ + public void initialize() { + + setLog4jProperties(); + setPropertiesFile(); + Properties prop = loadPropertiesFile(); + setTokenName(prop); + setTokenTtl(prop); + setVersionString(prop); + setAuthGroup(prop); + setPublicUser(prop); + setLdapKeyStore(prop); + setPrivateKey(prop); + } + + private Properties loadPropertiesFile() { + + Properties properties = new Properties(); + try { + properties.load(new FileInputStream(propertiesFile)); + } + catch (IOException e) { + throw new IllegalStateException(e); + } + return properties; + } + + private void setTokenName(Properties p) { + + tokenName = p.getProperty(TOKEN_NAME); + if (tokenName == null) + throw new IllegalArgumentException(TOKEN_NAME + " not specified"); + } + + private void setTokenTtl(Properties p) { + + tokenTtl = new Long(p.getProperty(TOKEN_TTL)); + if (tokenTtl == -1) + throw new IllegalArgumentException(TOKEN_TTL + " not specified"); + } + + private void setVersionString(Properties p) { + + webServiceVersion = p.getProperty(WEB_SERVICE_VERSION); + if (webServiceVersion == null) + throw new IllegalArgumentException(WEB_SERVICE_VERSION + + " not specified"); + } + + private void setAuthGroup(Properties p) { + + authGroup = p.getProperty(AUTH_GROUP); + if (authGroup == null) + throw new IllegalArgumentException(AUTH_GROUP + " not specified"); + } + + private void setPublicUser(Properties p) { + + publicUser = p.getProperty(PUBLIC_USER); + if (publicUser == null) + throw new IllegalArgumentException(publicUser + " not specified"); + } + + private void setPropertiesFile() { + + propertiesFile = FileUtility.assertCanRead(new File(getConfigDir(), + PROPERTIES)); + } + + private void setLdapKeyStore(Properties p) { + + String fileName = p.getProperty(LDAP_KEY_STORE); + if (fileName == null || fileName.isEmpty()) { + throw new IllegalArgumentException(LDAP_KEY_STORE + + " not specified properly"); + } + + ldapKeyStore = FileUtility.assertCanRead(new File(configDir, fileName)); + } + + private void setPrivateKey(Properties p) { + + String fileName = p.getProperty(PRIVATE_KEY); + + File f = null; + try { + f = FileUtility.assertCanRead(new File(configDir, fileName)); + privateKey = SymmetricEncrypter.readKey(f); + } + catch (UserErrorException e) { + privateKey = SymmetricEncrypter.makeKey(); + SymmetricEncrypter.writeKey(privateKey, f); + } + + } + + private void setLog4jProperties() { + + File properties = new File(getConfigDir(), "log4j.properties"); + PropertyConfigurator.configureAndWatch(properties.getAbsolutePath()); + } + + private void setConfigDir(String realPath) { + configDir = FileUtility.assertCanRead(realPath); + } + +} diff --git a/Gatekeeper/src/edu/lternet/pasta/gatekeeper/GatekeeperFilter.java b/Gatekeeper/src/edu/lternet/pasta/gatekeeper/GatekeeperFilter.java new file mode 100644 index 00000000..8ce2f28b --- /dev/null +++ b/Gatekeeper/src/edu/lternet/pasta/gatekeeper/GatekeeperFilter.java @@ -0,0 +1,349 @@ +/* + * + * $Date$ $Author$ $Revision$ + * + * Copyright 2010 the University of New Mexico. + * + * This work was supported by National Science Foundation Cooperative Agreements + * #DEB-0832652 and #DEB-0936498. + * + * 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 edu.lternet.pasta.gatekeeper; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.HttpHeaders; + +import org.apache.log4j.Logger; + +import edu.lternet.pasta.common.security.access.UnauthorizedException; +import edu.lternet.pasta.common.security.auth.AuthSystemDef; +import edu.lternet.pasta.common.security.auth.KnbAuthSystem; +import edu.lternet.pasta.common.security.auth.SymmetricEncrypter; +import edu.lternet.pasta.common.security.token.AuthToken; +import edu.lternet.pasta.common.security.token.AuthTokenFactory; +import edu.lternet.pasta.common.security.token.AuthTokenWithPassword; +import edu.lternet.pasta.common.security.token.BasicAuthToken; + +/** + *

    + * The Gatekeeper web service handles all authentication from incoming requests. + *

    + * + *

    + * If the user submits only BASIC authentication credentials, a token will be + * generated and returned upon completion of the requested query. + *

    + * + *

    + * If the user submits a token, the token will be used provided it does not + * exceed the time to live. In that event, a ServletException is thrown. + *

    + * + *

    + * If no credentials or tokens are submitted, a token for special user public + * will be created and the remainder of the query will be done as public. The + * response will return a public token. + *

    + * + * @webservicename Gatekeeper + * @baseurl https://pasta.lternet.edu/ + */ +public final class GatekeeperFilter implements Filter +{ + + private static Logger logger = Logger.getLogger(GatekeeperFilter.class); + private FilterConfig filterConfig; + private static int BAD_REQUEST_CODE = 400; + private static int UNAUTHORIZED_CODE = 401; + private boolean publicUser = false; + + private enum CookieUse { + EXTERNAL, INTERNAL + } + + /** + * Overridden init method that sets the filterConfig. + */ + @Override + public void init(FilterConfig config) throws ServletException { + filterConfig = config; + } + + /** + * Overridden destroy method that free's the filterConfig. + */ + @Override + public void destroy() { + filterConfig = null; + } + + /** + * Overridden doFilter method. + * @param request ServletRequest representing the incoming user http(s) + * request. + * @param request ServletResponse representing the associated response + * that will eventually be passed on to the + * next servlet. + */ + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse res = (HttpServletResponse) response; + try { + Cookie internalCookie = hasAuthToken(req.getCookies()) ? + doCookie(req) : doHeader(req, res); + chain.doFilter(new PastaRequestWrapper(req, internalCookie), res); + } + catch (IllegalStateException e) { + res.setStatus(BAD_REQUEST_CODE); + PrintWriter out = res.getWriter(); + out.println(e); + } + catch (UnauthorizedException e) { + res.setStatus(UNAUTHORIZED_CODE); + PrintWriter out = res.getWriter(); + out.println(e.getMessage()); + } + catch (IllegalArgumentException e) { + res.setStatus(UNAUTHORIZED_CODE); + PrintWriter out = res.getWriter(); + out.println(e.getMessage()); + } + + + } + + private Cookie doCookie(HttpServletRequest req) + throws IllegalArgumentException, IllegalStateException, UnauthorizedException { + + AuthToken token = null; + String authTokenStr = retrieveAuthTokenString(req.getCookies()); + /* Check Validity */ + token = decryptToken(authTokenStr); + /* Check TTL */ + assertTimeToLive(token); + return makeAuthTokenCookie(token, CookieUse.INTERNAL); + } + + private Cookie doHeader(HttpServletRequest req, HttpServletResponse res) { + + AuthToken authToken = + makeAuthenticated(req.getHeader(HttpHeaders.AUTHORIZATION)); + Cookie externalCookie = + makeAuthTokenCookie(authToken, CookieUse.EXTERNAL); + + if (!publicUser) res.addCookie(externalCookie); + return makeAuthTokenCookie(authToken, CookieUse.INTERNAL); + } + + private void assertTimeToLive(AuthToken attrlist) throws UnauthorizedException { + + if (attrlist == null) { + String s = "Token not found."; + throw new UnauthorizedException(s); + } + long ttl = attrlist.getExpirationDate() - (new Date().getTime()); + if (ttl < 1) { + String s = "Token has expired."; + throw new UnauthorizedException(s); + } + } + + private boolean hasAuthToken(Cookie[] cookies) { + if (retrieveAuthTokenString(cookies) == null) return false; + return true; + } + + private AuthToken decryptToken(String tokenStr) throws IllegalStateException { + + String errorMsg = "Invalid AuthToken Submitted."; + + if (tokenStr == null || tokenStr.isEmpty()) { + throw new IllegalStateException(errorMsg); + } + + String decrypted = null; + try { + decrypted = + SymmetricEncrypter.decrypt(tokenStr, + ConfigurationListener.getPrivateKey()); + } + catch (IllegalArgumentException e) { + throw new IllegalStateException(errorMsg); + } + + return AuthTokenFactory.makeCookieAuthToken(decrypted); + } + + private String retrieveAuthTokenString(Cookie[] cookies) { + + /* no cookies */ + if (cookies == null) return null; + for (Cookie c : cookies) { + if (c.getName().equals(ConfigurationListener.getTokenName())) { + /* found correct cookie */ + return c.getValue(); + } + } + return null; + } + + private AuthToken makeAuthenticated(String rawHeader) { + + String tmpHeader = null; + if (rawHeader == null || rawHeader.isEmpty()) { + tmpHeader = BasicAuthToken.makeTokenString( + ConfigurationListener.getPublicUser(), + ConfigurationListener.getPublicUser()); + publicUser = true; + } + else { + tmpHeader = rawHeader; + publicUser = false; + } + + KnbAuthSystem knb = new KnbAuthSystem(ConfigurationListener.getLdapKeyStore()); + + AuthTokenWithPassword basicToken = + AuthTokenFactory.makeAuthTokenWithPassword(tmpHeader); + String user = basicToken.getUserId(); + + Set groups = new HashSet(); + if (!user.equals(ConfigurationListener.getPublicUser())) { + + if (!knb.authenticate(user, basicToken.getPassword())) { + String s = "The user '" + user + + "' could not be authenticated " + + "using the LTER's or KNB's LDAP server."; + throw new UnauthorizedException(s); // Handle this better + } + // groups = knb.getGroups(user); // No groups currently stored here + groups.add(ConfigurationListener.getAuthGroup()); + } + AuthSystemDef authSystem = knb.getAuthSystemDef(); + long expirationDate = + new Date().getTime() + ConfigurationListener.getTokenTtl(); + AuthToken token = + AuthTokenFactory.makeCookieAuthToken(user, authSystem, + expirationDate, groups); + + return token; + } + + private Cookie makeAuthTokenCookie(AuthToken attrlist, CookieUse use) { + + String cookieValue = null; + switch (use) { + case EXTERNAL: + cookieValue = SymmetricEncrypter.encrypt(attrlist.getTokenString(), + ConfigurationListener.getPrivateKey()); + break; + case INTERNAL: + cookieValue = attrlist.getTokenString(); + break; + } + Cookie c = new Cookie(ConfigurationListener.getTokenName(), cookieValue); + Long expiry = attrlist.getExpirationDate() / 1000L; + c.setMaxAge(expiry.intValue()); + return c; + } + + public static class PastaRequestWrapper extends HttpServletRequestWrapper + { + + private static Logger logger = Logger.getLogger(PastaRequestWrapper.class); + private Cookie cookie; + + public PastaRequestWrapper(HttpServletRequest request, Cookie cookie) { + + super(request); + this.cookie = cookie; + } + + public String getHeader(String name) { + + if (name.equals(HttpHeaders.AUTHORIZATION)) return null; + String header = super.getHeader(name); + if (name.equals(HttpHeaders.COOKIE) && header.isEmpty() + && (cookie != null)) + return cookie.getName(); + + return header; + } + + public Enumeration getHeaders(String name) { + + Enumeration enumStr = super.getHeaders(name); + + if (name.equals(HttpHeaders.AUTHORIZATION)) { + List ls = new ArrayList(); + enumStr = Collections.enumeration(ls); + } + + if (!name.equals(HttpHeaders.COOKIE) || (cookie == null)) + return enumStr; + + ArrayList list = Collections.list(enumStr); + list.add(cookie.getName() + "=" + cookie.getValue()); + return Collections.enumeration(list); + } + + public Enumeration getHeaderNames() { + + Enumeration enumStr = super.getHeaderNames(); + ArrayList list = Collections.list(enumStr); + if (!list.contains(HttpHeaders.COOKIE) && (cookie != null)) { + list.add(HttpHeaders.COOKIE); + } + + return Collections.enumeration(list); + } + + public Cookie[] getCookies() { + + Cookie[] cookies = super.getCookies(); + if (cookie == null) return cookies; + + ArrayList list = (cookies == null) ? new ArrayList() + : new ArrayList(Arrays.asList(cookies)); + + list.add(cookie); + cookies = new Cookie[list.size()]; + return list.toArray(cookies); + } + + } + +}