[jira_http_authentication] 01/13: base commit of jira_http_authenticator

Scott Cantor cantor.2 at osu.edu
Fri Aug 4 13:50:32 EDT 2017


This is an automated email from the git hooks/post-receive script.

scantor pushed a commit to branch master
in repository jira_http_authentication.

View the commit online:
http://git.shibboleth.net/view/?p=jira_http_authentication.git;a=commit;h=5026fd58f1e3fb65518c7af076c11aa7a95f6aca

commit 5026fd58f1e3fb65518c7af076c11aa7a95f6aca
Author: Kevin Foote <kpfoote at uoregon.edu>
AuthorDate: Fri Jun 20 10:07:36 2014 -0700

    base commit of jira_http_authenticator
---
 .gitignore                                         |   30 +
 LICENSE                                            |  234 ++++
 README.md                                          |   26 +
 conf/remoteUserAuthenticator.properties            |  238 ++++
 history/LICENSE                                    |   26 +
 history/README-old                                 |  120 ++
 history/README-old_history                         |  684 ++++++++++
 history/build.xml.old                              |   92 ++
 pom.xml                                            |  105 ++
 .../shibboleth/tools/jira/authn/GroupMapper.java   |  232 ++++
 .../tools/jira/authn/RemoteUserAuthenticator.java  | 1317 ++++++++++++++++++++
 .../tools/jira/authn/ShibAuthConfigLoader.java     |  451 +++++++
 .../tools/jira/authn/ShibAuthConfiguration.java    |  426 +++++++
 .../tools/jira/authn/ShibAuthConstants.java        |  182 +++
 .../shibboleth/tools/jira/authn/StringUtil.java    |  135 ++
 src/main/resources/applicationContext.xml          |   13 +
 .../shibboleth/RemoteAuthenticatorTest.java        |    9 +
 17 files changed, 4320 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cec5823
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,30 @@
+# Compiled source #
+###################
+*.class
+
+# OS generated files #
+######################
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+
+# FROM CHAUTH       #
+#####################
+.classpath
+.project
+.settings/
+.idea/
+*.iml
+*.iws
+*.ipr
+.DS_Store
+log/
+target/
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+nbactions.xml
+nb-configuration.xml
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..529fea9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,234 @@
+Copyright (c) 2008-2014, Confluence HTTP Authenticator Team
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the Confluence HTTP Authenticator Team
+      nor the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+------------------------------------------------------------------------------------
+
+The original remoteUserAuthenticator by Chad LaJoie and Georgetown University
+was released under the Apache 2.0 license. All development and contributions
+since have been made under the BSD license.
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..bca6b01
--- /dev/null
+++ b/README.md
@@ -0,0 +1,26 @@
+JIRA HTTP Authenticator
+=====
+
+### Overview
+
+This is a port of the [Confluence HTTP Authenticator](https://github.com/chauth/confluence_http_authenticator ) that has hooks to work with the JIRA auth system. The package works much like its Confluence counterpart using Seraph entry points.  
+
+### Build / Install
+
+For now, refer to the various [Confluence](https://github.com/chauth/confluence_http_authenticator) install docs when you get stuck. This will at a minimum give you something to get started with.
+
+Currently use of this authenticator plugin requires knowledge of setting up and using a [maven](http://maven.apache.org) build environment. Please familiarize yourself with [maven](http://maven.apache.org) before moving forward with your use of this plugin. 
+
+#### Build
+
+```
+mvn clean
+
+mvn install
+ 
+```
+
+### Disclaimer
+
+Docs are limited at the moment. 
+This software is available as is. 
diff --git a/conf/remoteUserAuthenticator.properties b/conf/remoteUserAuthenticator.properties
new file mode 100644
index 0000000..4dfbc7b
--- /dev/null
+++ b/conf/remoteUserAuthenticator.properties
@@ -0,0 +1,238 @@
+# Configuration file for Confluence Shibboleth Authenticator
+
+# Whether to create user accounts for new users
+# Acceptable values: true, false
+create.users=true
+
+# Whether existing accounts should have their name and email address updated upon login. This is strongly suggested if
+# create.users is true.
+# Acceptable values: true, false
+update.info=true
+
+# Whether new and existing accounts should have their last login date and previous login date updated in user properties
+# upon login.
+# Acceptable values: true, false
+update.last.login.date=true
+
+# Whether the configuration file should be automatically reloaded when it's changed.
+reload.config=false
+
+# When reloading the configuration file, how long to wait (in milliseconds) between checking the configuration file for
+# changes.
+reload.config.check.interval=5000
+
+# The default group(s) to for newly created users, only used if create.users is true.
+#
+# Notes:
+# * Group name list can be comma or semicolon delimited.
+# * Currently groups must pre-exist.
+default.roles=confluence-users
+
+# HTTP header names where the user's full name, email address and username will come from. The full name and email
+# address headers need not be populated (can be provided as empty values in the headers by Shibboleth for example if
+# Shibboleth can provide no full name or email address for a user). If header.fullname is unspecified or the full name
+# provided by the header value is empty or null then it will default to specifying the user id as the full name. If the
+# header.email is unspecified or the value of the header is null the user's email address will be null.
+#
+# Although these two user fields may not seem so important at first, note that whenever a user modifies a wiki page,
+# their full name is displayed as the person that last modified the page, and similarly their full name is noted next to
+# any comment that they add to a page. Email address is important because when a user chooses to put a "watch" on a wiki
+# page, they get emails when that page is changed.
+#
+# The headers matching the shibboleth defaults would be:
+# header.fullname=Shib-InetOrgPerson-displayName
+# header.email=Shib-InetOrgPerson-mail
+#
+# Note: if the header value contains commas or semicolons, then it will choose the first value in the comma or
+# semicolon-delmited list.
+#
+# Note: if fullname mapping is used (see below) then it will try using that first to get full name using this header.
+header.fullname=CONF_FULL_NAME
+header.email=CONF_EMAIL
+header.remote_user=REMOTE_USER
+
+# Whether or not to force the username we receive from Shibboleth to be all-lowercase. This was always true in
+# versions <= 1.7.2 and still defaults to true
+username.convertcase=true
+
+# Indication whether the group memberships of the user should be updated after creation. Acceptable values: true/false.
+# If true, then group memberships will be added (default.roles, and header.dynamicroles depending on the mapping
+# headers) whenever the user authenticates, and not just if the user is created by the authenticator.
+update.roles=true
+
+# Indication whether HTTP header values should be converted to UTF-8 to avoid an issue noted by Helsinki University:
+# "where there is something not using utf-8 involved, 16-bit characters get bytes 83 c2 inserted between."
+convert.to.utf8=false
+
+# OPTIONAL
+# This feature takes effect only when update.roles is true. List of dynamicroles headers, along side with the labels of
+# their group-mapper. Each header can have a set of mapper labels to be activated. A header entry without appropriate
+# assigned label will be ignored
+#
+# e.g. say we'd like to perform automatic group provisioning based on headers: "SHIB-EP-ENTITLEMENT", "affiliation", and
+# our defined "fix-role-header" (note these have to match whatever defined in AAP), then we can define the following:
+#
+#dynamicroles.header.SHIB-EP-ENTITLEMENT = label1, label2, label4, label3
+#dynamicroles.header.affiliation = label1, label5
+
+# Whether the dynamicroles attempt to automatically create the role in confluence if such role does not exist.
+dynamicroles.auto_create_role=false
+
+# Instruct to convert all output groups into lowercase before creating them on confluence. This is necessary to overcome
+# some versions of Confluence's limitation of disallowing group names in upper case. Defaults to true.
+dynamicroles.output.tolowercase=true
+
+# Define mapper label and its logic for dynamicroles. Each mapper has to define either
+# "match" or "transform" property, otherwise it will not be included in the
+# dynamicroles processing.
+#
+# Notes:
+#
+# * Colons need to be escaped by \
+# * Attribute names are case-insensitive.
+# * Value list can be comma or semicolon delimited.
+#
+# Description of each property:
+#
+# * match = java regex string to match against the ENTIRE input, you can use java
+#           regex groupings (http://java.sun.com/j2se/1.4.2/docs/api/java/util/regex/Pattern.html#cg)
+#           e.g. to explicitly match a fix string: match= Hello World
+#                to match with grouping: match = some\:urn\:(\\w+):(.*)
+#
+# * casesensitive = boolean, whether the match regex should care about case
+#                   sensitivity matching, default to true
+#
+# * transform = a fix string replacement of the input (e.g. the group or groups).
+#               when not specified, it will simply take the input value.
+#               transform can be a list of groups separated by comma or semicolon e.g.
+#               transform= A, B, C which means if this mapper matches, the output
+#               would be group A, B, and C. You can also use $1..$N to represent
+#               a matched regex group (as done by "match" regex). $0 refers to
+#               the entire input string.
+#               e.g. spit out a fix output: transform = Hello There
+#                    reuse some regex grouping: transform = confluence-$1, confluence-$2
+#                    (suppose the input is "some:urn:users:administrators", then
+#                     using the last example match regex we are converting
+#                     this to "confluence-users" and "confluence-administrators"
+#
+# You can leave the .match property empty, which simply means the input is
+# passed directly for .transform to process. Similarly, if you leave .transform
+# undefined, then there won't be transformation performed on the input (e.g.
+# you may want to allow those inputs matching your regex to be included in
+# confluence and filter out those that don't match.
+#
+# examples:
+#
+# map "some:urn:group1:group2" to groups called "group1" and "group2"
+#
+#dynamicroles.mapper.label1.match=some\:urn\:(\\w+)\:(.*)
+#dynamicroles.mapper.label1.transform=$1, $2
+#
+# map "StaFF" to "cs100"
+#
+#dynamicroles.mapper.map2.match = staff
+#dynamicroles.mapper.map2.casesensitive = false
+#dynamicroles.mapper.map2.transform = cs100
+#
+
+# OPTIONAL
+# Normally, users added to a group based on Shibboleth attributes would stay in
+# the group forever, even if they no longer have the attribute.  The
+# purge.roles feature allows to specify a list of groups which should be
+# automatically purged of any users who no longer have the attributes to regain
+# entry (comma or semicolon separated).
+# When logging in, a user will be automatically removed from the group IFF the
+# user would not be added to the group (either via dynamicroles.header or
+# default.roles).  This feature takes effect only when update.roles is true.
+#
+# Example:
+# remove members from role starting with "alum", "alumni",
+# or any other "alum*", as well as from cs101. (ignore case sensitivity).
+#
+#purge.roles = alum.* , cs101
+
+# OPTIONAL
+# Do mapping on values presented in REMOTE_USER to something understandable
+# by confluence. Sometimes remote user is mapped to an attribute containing
+# characters invalid in confluence, use this feature below to do transformation
+# for it (assuming the original remote-user value hits your confluence without
+# much of your control).
+#
+# This feature has similar syntax to dynamic roles.
+#
+# Please make sure that the resultant remote user is:
+# - unique & single-value
+# - accepted by confluence (fit into 128 chars length, no weird chars, etc)
+#
+# If a regex map doesn't match the input provided, then
+# the mapping is not performed (e.g. the input is untouched; make sure
+# you understand the mapping logic).
+#
+# Example: suppose the remote user has initial value
+#   "https://idp.edu/idp!https://sp.edu/shibboleth!1234-56789-#00%00-TTT"
+# and we would like it to be transformed to
+#   "123456789A00c00 at idp.edu"
+# then we can define the following:
+#
+#remoteuser=remoteusermap
+#remoteuser.replace=#,A,%,c,(-|TTT),,
+#remoteuser.map.remoteusermap.match = ^(http|https)://(.*?)(:|/)?[^!]*?!([^!]*?)!(.*)
+#remoteuser.map.remoteusermap.casesensitive = false
+#remoteuser.map.remoteusermap.transform = $5@$2
+#
+# remoteusermap is the mapping label to be used, multiple labels
+# can be used but only 1st result from the label is chosen as remote user)
+#
+# .replace is pair-wise regex & replacement strings to be applied to the FINAL
+# remote-user once the mapping has been performed. null (as replacement string)
+# can be represented by simply empty string (e.g. '-' and 'TTT' above are removed)
+#
+
+# OPTIONAL
+# Do mapping on values presented in header defined as value of header.fullname. This is for those that don't have a
+# "display name" type attribute that can be exposed to Confluence's Shibboleth SP, but must put a full name together
+# from multiple values, etc.
+#
+# This feature has similar syntax to dynamic roles.
+#
+# If a regex map doesn't match the input provided, then
+# the mapping is not performed, and it will use the first value of that header.
+#
+# Example 1: suppose the full name has the header value
+#   "Doe; John"
+# and we would like it to be transformed to
+#   "John Doe"
+# then we can define the following:
+#
+#fullname=fullnamemap
+#fullname.map.fullnamemap.match = ^(.*);(.*)
+#fullname.map.fullnamemap.casesensitive = false
+#fullname.map.fullnamemap.transform = $2 $1
+#
+# Note: if the expression doesn't match, it will split the string by comma or semi-colon and get the first value, so
+# the fullname would be:
+#   "Doe"
+#
+# Example 2: suppose the full name has the header value
+#   "Doe#,%John"
+# and we would like it to be transformed to
+#   "John Doe"
+# then we can define the following:
+#
+#fullname=fullnamemap
+#fullname.replace=#,,%,,
+#fullname.map.fullnamemap.match = ^(.*),(.*)
+#fullname.map.fullnamemap.casesensitive = false
+#fullname.map.fullnamemap.transform = $2 $1
+#
+# Note: if the expression doesn't match, it will split the string by comma or semi-colon and get the first value, so
+# the fullname would be:
+#   "Doe#"
+#
+# fullnamemap is the mapping label to be used, multiple labels
+# can be used but only 1st result from the label is chosen as remote user)
+#
+# .replace is pair-wise regex & replacement strings to be applied to the FINAL
+# full name once the mapping has been performed. null (as replacement string)
+# can be represented by simply empty string (e.g. '-' and 'TTT' above are removed)
+#
diff --git a/history/LICENSE b/history/LICENSE
new file mode 100644
index 0000000..764c449
--- /dev/null
+++ b/history/LICENSE
@@ -0,0 +1,26 @@
+Copyright (c) 2008-2012, Shibboleth Authenticator for Confluence Team
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the Custom Space User Management Plugin Development Team
+      nor the names of its contributors may be used to endorse or promote
+      products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/history/README-old b/history/README-old
new file mode 100644
index 0000000..b5e87e0
--- /dev/null
+++ b/history/README-old
@@ -0,0 +1,120 @@
+
+README for Shibboleth Authenticator for Confluence
+
+Documentation and main page at:
+https://studio.plugins.atlassian.com/wiki/display/SHBL/Confluence+Shibboleth+Authenticator
+
+Build assumes Java 6, Maven 3. Atlassian SDK does not need to be installed, as it is an authenticator jar loaded on
+classpath, not a plugin, nor can it or should it be, even in Confluence 4.x.
+
+To get a developer account to contribute to the project, submit a bug in our Jira http://developer.atlassian.com/jira/browse/SHBL and then say you'd like to become a contributor, and also comment on the project's wiki page. If you don't get a response within a month, consider that it is ok to start development.
+
+If you want to get a svn account with Atlassian email developer-relations at atlassian.com and refer them to the ticket or email providing the "ok" for your work, indicating that the "Confluence Shibboleth Authenticator Team" wanted you to have committer rights to the Confluence Shibboleth Authenticator project ( https://studio.plugins.atlassian.com/svn/SHBL ) if you don't already have access.
+
+For larger changes, you'd want to email the group to get an ok, in which case let use know and we can provide you with some email addresses of the group. The group may be composed of more guys than currently listed on the wiki page, because the list was lost during Atlassian's transition to plugins.atlassian.com. But can be seen via svn log, pom.xml, and release notes in the wiki. Mostly all of us are just contributors and only a few of us have spent much time with it after Chad LaJoie l [...]
+
+Although there is a Google group that we have if needed to discuss development as a team:
+http://groups.google.com/group/confluence-shibauth-dev
+which has a mailing list for development discussion:
+confluence-shibauth-dev at googlegroups.com
+please keep conversation out of that group and mailing list if it can instead be discussed within a Jira ticket created for the plugin in the plugin's Jira instance:
+https://studio.plugins.atlassian.com/browse/SHBL
+
+Releases are kept here, mostly because we cannot use Atlassian's automated plugin installation process:
+https://studio.plugins.atlassian.com/svn/SHBL/releases
+
+Tags are here:
+https://studio.plugins.atlassian.com/svn/SHBL/tags
+
+Development is done in trunk, here:
+https://studio.plugins.atlassian.com/svn/SHBL/trunk
+
+================
+
+To build, type:
+
+    mvn clean install
+
+When committing, please try to include a Jira ticket number when possible in the beginning on the comment, e.g.:
+
+    svn commit -m "SHBL-12345 - Added developer to pom.xml"
+
+That is very important for reviewing svn log to come up with release notes later, so please don't commit without a
+ticket number.
+
+To release a new version:
+
+1) Be sure to add yourself to the list of developers in the pom.xml and to add whomever contributed a patch.
+
+2) Build and test (or have someone test)
+
+3) Tag trunk:
+
+    svn copy --username (yourusername) https://studio.plugins.atlassian.com/svn/SHBL/trunk/ https://studio.plugins.atlassian.com/svn/SHBL/tags/(new version) -m "Tagging (new version)"
+
+Example for tagging 1.7.1:
+
+    svn copy --username your-atlassian-svn-username-goes-here https://studio.plugins.atlassian.com/svn/SHBL/trunk/ https://studio.plugins.atlassian.com/svn/SHBL/tags/1.7.1 -m "Tagging 1.7.1"
+
+4) Checkout or update tags dir and go there and edit pom.xml to change version to remove "-SNAPSHOT" from project version:
+
+    cd ../tags
+    svn up
+    cd (new version)
+    edit pom.xml
+
+(remove -SNAPSHOT from project version)
+
+    svn commit -m "updated version to (version number)"
+
+For example, in 1.7.1 we would do:
+
+    svn commit -m "updated version to 1.7.1"
+
+While you're in here, do an svn log and try to determine what changed since the last release that is important enough to include in the release notes. Save this somewhere for reference in later steps.
+
+5) Build and add to releases
+
+(in tags/(new version))
+
+    mvn clean install
+    cp target/(name of jar) ../../releases
+    cd ../../releases
+    svn add (name of jar)
+    svn commit
+
+(commit message should be something like "releasing (version number)")
+
+For example, in 1.7.1 we would do:
+
+    svn commit -m "releasing 1.7.1"
+
+6) Change the wiki page to list the new version, release info, and change the information on current version at top:
+
+https://studio.plugins.atlassian.com/wiki/display/SHBL/Confluence+Shibboleth+Authenticator
+
+7) Add release to:
+
+https://plugins.atlassian.com/plugins/shibauth.confluence.authentication.shibboleth
+
+8) Update version number in pom.xml in trunk to the next snapshot version. Just increment the build version, so
+   1.7-SNAPSHOT becomes 1.7.1-SNAPSHOT or if is already has a third number (build version) then increment it so that
+   1.7.9-SNAPSHOT becomes 1.7.10-SNAPSHOT. Bug fixes are using a build version (third number) increment, API changes
+   are usually a minor version change, and major changes in how the plugin works that are likely incompatible with
+   previous versions are major version change.
+
+9) For the Jira project do the following:
+
+* Go to https://studio.plugins.atlassian.com/browse/SHBL
+* Versions tab (on left)
+* Manage Versions link (on right)
+* Copy/paste text from release info on page into description and set release date to release date
+* Add version
+* Browse Project
+* Resolve all bugs related to that version setting the target version to the version you released.
+
+10) Have fun!
+
+Troubleshooting:
+
+Be sure to build with Java 6.
diff --git a/history/README-old_history b/history/README-old_history
new file mode 100644
index 0000000..e4b0b02
--- /dev/null
+++ b/history/README-old_history
@@ -0,0 +1,684 @@
+------------------------------------------------------------------------
+r35285 | gswduke | 2009-09-23 10:03:13 -0400 (Wed, 23 Sep 2009) | 1 line
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/README
+
+updated README
+------------------------------------------------------------------------
+r35284 | gswduke | 2009-09-23 09:25:30 -0400 (Wed, 23 Sep 2009) | 1 line
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/TAGGING
+
+updated TAGGING doc
+------------------------------------------------------------------------
+r35283 | gswduke | 2009-09-23 09:21:05 -0400 (Wed, 23 Sep 2009) | 1 line
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/README
+
+Added correct URL for plugins.atlassian.com
+------------------------------------------------------------------------
+r34030 | gswduke | 2009-08-25 13:08:24 -0400 (Tue, 25 Aug 2009) | 1 line
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java
+
+SHBL-24 - fixing authentication for local accounts by integrating patch by Freie Universität Berlin and making it optional to use ShibLoginFilter, but turning off support for it by default. SHBL-28 - adding regexp matching for full name for Freie Universität Berlin so that they could use our plugin without mods
+------------------------------------------------------------------------
+r33617 | gswduke | 2009-08-14 09:45:26 -0400 (Fri, 14 Aug 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/README
+
+added release info for http://plugins.atlassian.com
+
+------------------------------------------------------------------------
+r33571 | gswduke | 2009-08-12 15:51:44 -0400 (Wed, 12 Aug 2009) | 1 line
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+updating pom.xml with contributions for this release
+------------------------------------------------------------------------
+r33570 | gswduke | 2009-08-12 15:34:20 -0400 (Wed, 12 Aug 2009) | 1 line
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+
+SHBL-26 - integrating patch from Michael Gettes not to try to update read-only users
+------------------------------------------------------------------------
+r33362 | gswduke | 2009-08-04 08:58:27 -0400 (Tue, 04 Aug 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/GroupMapper.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+
+Had to enable debug of HTTP Headers and regexp match so that USC could debug their headers/regexp. Cleaned up logging a bit around those so that all HTTP headers logged at once (which might be more helpful in higher traffic).
+
+------------------------------------------------------------------------
+r33090 | bliong | 2009-07-23 00:41:20 -0400 (Thu, 23 Jul 2009) | 1 line
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/StringUtil.java
+
+Allow empty strings in the list rather than nulls. Null always requires special treatment in code, hence a lot simplified to use empty string.
+------------------------------------------------------------------------
+r33089 | bliong | 2009-07-22 23:18:21 -0400 (Wed, 22 Jul 2009) | 1 line
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+
+just deleting a few words in properties (forgotten & left unchecked previously)
+------------------------------------------------------------------------
+r33088 | bliong | 2009-07-22 23:12:03 -0400 (Wed, 22 Jul 2009) | 1 line
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/GroupMapper.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibLoginFilter.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/StringUtil.java
+
+Solution to SHBL-25. An option is provided to allow remote user to be transformed/mapped based on regex to be used by Confluence userid. Read config properties file for example and detail.
+------------------------------------------------------------------------
+r32316 | gswduke | 2009-06-26 14:08:21 -0400 (Fri, 26 Jun 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+updated version and comment
+
+------------------------------------------------------------------------
+r32231 | gswduke | 2009-06-24 13:30:47 -0400 (Wed, 24 Jun 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+changed version to 1.5.1-SNAPSHOT
+
+------------------------------------------------------------------------
+r32230 | gswduke | 2009-06-24 13:29:23 -0400 (Wed, 24 Jun 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+
+SHBL-23 - changing No mapper capable of processing role message to being logged as DEBUG
+
+------------------------------------------------------------------------
+r32229 | gswduke | 2009-06-24 13:24:38 -0400 (Wed, 24 Jun 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+fixed bad pom
+
+------------------------------------------------------------------------
+r32227 | gswduke | 2009-06-24 10:25:36 -0400 (Wed, 24 Jun 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+fixing developer comments again
+
+------------------------------------------------------------------------
+r32226 | gswduke | 2009-06-24 10:11:39 -0400 (Wed, 24 Jun 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+updated pom.xml version to 1.6-SNAPSHOT
+
+------------------------------------------------------------------------
+r32222 | gswduke | 2009-06-24 10:08:53 -0400 (Wed, 24 Jun 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+fixing version of snapshot, fixing pom developer notes
+
+------------------------------------------------------------------------
+r32160 | gswduke | 2009-06-22 10:52:41 -0400 (Mon, 22 Jun 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+
+SHBL-21 - applying patch contributed from Michael Gettes because call to hasMembership and only calling addMembership when needed he determined was faster than only calling addMembership. 
+
+------------------------------------------------------------------------
+r29057 | gswduke | 2009-03-16 09:41:29 -0400 (Mon, 16 Mar 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+
+fix for SHBL-20 and some other possible config related issues not yet noticed. changed from using static values in config to instance. config instance itself is static and gets reloaded, so we don't want static member variables in the configuration instance itself, unless I'm missing something. we should consider moving to spring config for v2.0. config is a little out of hand now.
+
+------------------------------------------------------------------------
+r28524 | gswduke | 2009-02-24 14:37:20 -0500 (Tue, 24 Feb 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+
+resolving SHBL-18. added null checks for header.fullname and header.email in config and updated config
+
+------------------------------------------------------------------------
+r28522 | gswduke | 2009-02-24 14:10:52 -0500 (Tue, 24 Feb 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+
+added more comments
+
+------------------------------------------------------------------------
+r28521 | gswduke | 2009-02-24 14:01:36 -0500 (Tue, 24 Feb 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+
+Added additional comments by fullname and email headers
+
+------------------------------------------------------------------------
+r27647 | gswduke | 2009-01-28 11:33:50 -0500 (Wed, 28 Jan 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+Per some recent commits, there seems to now be a dependency on a newer version of seraph than may be included in earlier versions of confluence. In fact, upping the confluence version (tried several versions) didn't seem to include these. I looked at Atlassian's pom for confluence and it appears that only an earlier version of seraph (not atlassian-seraph) was referenced in earlier builds, and later builds don't include the version they may/may not use. We may end up needing to make chan [...]
+
+------------------------------------------------------------------------
+r27519 | bliong | 2009-01-22 05:38:08 -0500 (Thu, 22 Jan 2009) | 8 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibLoginFilter.java
+
+resolve SHBL-16, hopefully improve response time slightly for SHBL-15. Also this has made updateLastLogin option to be optional, we're letting confluence to take care of that now.
+
+you will need to use the provided LoginFilter, edit web.xml:
+<filter-name>login</filter-name>
+<!-- <filter-class>com.atlassian.seraph.filter.LoginFilter</filter-class>  -->
+<filter-class>shibauth.confluence.authentication.shibboleth.ShibLoginFilter</filter-class>
+
+Failure to use this Login Filter in web.xml will produce previous behaviour (i.e. before SHBL-16 fix; updateLastLogin is fully obeyed).
+------------------------------------------------------------------------
+r27123 | bliong | 2009-01-09 21:42:03 -0500 (Fri, 09 Jan 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/StringUtil.java
+
+* provide option dynamicroles.output.tolowercase to convert all group output to lower case by default.
+* change StringUtil.convertToUTF8 to have new String(getBytes("UTF-8"),"UTF-8") to see if UTF-8 conversion works, otherwise byte by byte conversion needs to be put in placed.
+------------------------------------------------------------------------
+r27058 | bliong | 2009-01-08 01:30:41 -0500 (Thu, 08 Jan 2009) | 5 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/GroupMapper.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/StringUtil.java
+
+Revamp of group mapping, ref SHBL-6. Include capabilities of:
+- mapping with regex
+- inspecting only necessary headers for group mapping
+- purging roles with regex
+- multiple regex/mappers can be specified per header
+------------------------------------------------------------------------
+r26939 | gswduke | 2009-01-05 09:50:01 -0500 (Mon, 05 Jan 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+
+correct attribution to Jesse Lahtinen of Technical University (jesse at tkk.fi) for SHBL-14 fix. I had just assumed Juha did it. Sorry!
+
+------------------------------------------------------------------------
+r26906 | gswduke | 2009-01-02 10:07:34 -0500 (Fri, 02 Jan 2009) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/StringUtil.java
+
+Implementing patch submitted by Erkki Aalto written by Juha Ojaluoma for SHBL-14. Note that USER-254 possible bug related to this feature. This feature implements a new config option called update.last.login.date (true/false) in config that sets properties for the user in os_propertyentry even if not using osuser schema otherwise according to Erkki. Also updated License text.
+
+------------------------------------------------------------------------
+r26545 | vladimir.mencl | 2008-12-11 21:07:51 -0500 (Thu, 11 Dec 2008) | 3 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java
+
+SHBL-11: implemented an additional setting "reload.config.check.interval" to set (in milliseconds) how long to wait between checking the configuration file for changes.
+
+
+------------------------------------------------------------------------
+r26536 | vladimir.mencl | 2008-12-11 01:27:40 -0500 (Thu, 11 Dec 2008) | 19 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java
+
+Implemented two new features requested in SHBL-10 and SHBL-11
+
+SHBL-10: Addresses the issue where dynamically assigned to groups would stay in these groups even when they no longer have attributes that originally gained them the membership (such as codes of courses they are taking).
+
+Now, such groups can be specified with purge.roles, such as:
+
+purge.roles=course-ABCD123,course-DCBA321
+
+(Requires update.roles=true)
+
+SHBL-11: Restarting Confluence after adding a dynamic mapping would have too much impact on a production environment.
+
+With this feature, the module checks for changes to the configuration file (remoteAuthentication.propeties) on each login and reloads the file if needed.
+
+To activate this feature, add the following to your configuration file:
+
+reload.config=true
+
+
+------------------------------------------------------------------------
+r26431 | gswduke | 2008-12-05 10:05:02 -0500 (Fri, 05 Dec 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/README
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+updated README and version to 1.4...
+
+------------------------------------------------------------------------
+r26396 | gswduke | 2008-12-04 09:18:52 -0500 (Thu, 04 Dec 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+
+SHBL-9 - Applied shibboleth-authenticator-updateRoles-fix2.diff from Vlad that was attached to SHBL-9, but changed the confluence version to 2.3 instead of 2.9.2, because I am having trouble compiling it against 2.9.2, and since it compiles against 2.3 and no confluence classes are included in the jar, only the class, package names, and method signatures it relies on matter afaik, with the exception of the use of the ContainerManager to get groupManager, which depends on Spring configura [...]
+
+------------------------------------------------------------------------
+r26343 | gswduke | 2008-12-03 09:56:12 -0500 (Wed, 03 Dec 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+updated version to 1.4-SNAPSHOT
+
+------------------------------------------------------------------------
+r26339 | gswduke | 2008-12-03 09:48:56 -0500 (Wed, 03 Dec 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+oops removed 1.3 tag. needed to put in developer info first. changing version back to 1.3-SNAPSHOT
+
+------------------------------------------------------------------------
+r26337 | gswduke | 2008-12-03 09:46:45 -0500 (Wed, 03 Dec 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+changed pom version to 1.4-SNAPSHOT
+
+------------------------------------------------------------------------
+r26335 | gswduke | 2008-12-03 09:33:11 -0500 (Wed, 03 Dec 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+
+SHBL-8 - incorporated patch from Vladimir Mencl in SHBL-8. I added a few comments. Issue is that in Confluence 2.9.2 unless anonymous users have permission to use confluence, when it calls addMembership to add the user to a role, the GroupManager instance is not being returned. The fix incorporates similar code to that of the example that Matt Ryall of Atlassian suggested (in http://jira.atlassian.com/browse/CONF-12158 ) and was part of an example authenticator ConfluenceGroupJoiningAuth [...]
+
+------------------------------------------------------------------------
+r21549 | gswduke | 2008-07-31 11:53:00 -0400 (Thu, 31 Jul 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+
+SHBL-7 - added ability for it to handle email or fullname first header value if it contains multiple email addresses or fullnames separated by comma or semicolon, as SP tends to send it according to Scott Cantor.
+
+------------------------------------------------------------------------
+r21548 | gswduke | 2008-07-31 11:40:01 -0400 (Thu, 31 Jul 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+
+SHBL-7 - added ability to handle multiple headers, even though Scott said that probably won't happen
+
+------------------------------------------------------------------------
+r21541 | gswduke | 2008-07-31 10:28:51 -0400 (Thu, 31 Jul 2008) | 3 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/StringUtil.java
+
+added changes to utf8 conversion from Bruc Liong and Gary Weaver
+
+
+------------------------------------------------------------------------
+r21479 | gswduke | 2008-07-30 11:45:18 -0400 (Wed, 30 Jul 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+
+updated to add convert.to.utf8 property to default config, per SHBL-5. Also note in previous commit that changed from Apache license to BSD license.
+
+------------------------------------------------------------------------
+r21477 | gswduke | 2008-07-30 11:23:10 -0400 (Wed, 30 Jul 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/StringUtil.java
+
+Integrating patch from Juha Ojaluoma for SHBL-5. Gary changed patch to convert all header values to UTF-8 instead of just full name. Fixed jar name
+
+------------------------------------------------------------------------
+r16651 | gswduke | 2008-03-07 15:19:09 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/build.xml.old
+
+added comment to old build.xml
+
+------------------------------------------------------------------------
+r16650 | gswduke | 2008-03-07 15:18:23 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/trunk/README
+   D /contrib/confluence/shibboleth-authenticator/trunk/build.xml
+   A /contrib/confluence/shibboleth-authenticator/trunk/build.xml.old (from /contrib/confluence/shibboleth-authenticator/trunk/build.xml:16648)
+
+have switched to Maven 2 build, because Atlassian plugin development better supports it (easy to change dependencies)
+
+------------------------------------------------------------------------
+r16649 | gswduke | 2008-03-07 15:16:48 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   D /contrib/confluence/shibboleth-authenticator/trunk/test
+
+removing old test src dir
+
+------------------------------------------------------------------------
+r16648 | gswduke | 2008-03-07 15:16:07 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/test/java/shibauth/confluence/authentication/shibboleth/RemoteAuthenticatorTest.java
+
+changed package in test
+
+------------------------------------------------------------------------
+r16647 | gswduke | 2008-03-07 15:15:14 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   D /contrib/confluence/shibboleth-authenticator/trunk/build-lib
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/test
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/test/java
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/test/java/shibauth
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/test/java/shibauth/confluence
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/test/java/shibauth/confluence/authentication
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/test/java/shibauth/confluence/authentication/shibboleth
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/test/java/shibauth/confluence/authentication/shibboleth/RemoteAuthenticatorTest.java (from /contrib/confluence/shibboleth-authenticator/trunk/test/edu/georgetown/middleware/confluence/RemoteAuthenticatorTest.java:16630)
+   D /contrib/confluence/shibboleth-authenticator/trunk/test/edu/georgetown/middleware/confluence/RemoteAuthenticatorTest.java
+
+moving java test source package
+
+------------------------------------------------------------------------
+r16646 | gswduke | 2008-03-07 15:12:10 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/trunk/pom.xml
+
+adding pom.xml
+
+------------------------------------------------------------------------
+r16645 | gswduke | 2008-03-07 15:11:31 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/StringUtil.java
+
+changed package in java source
+
+------------------------------------------------------------------------
+r16644 | gswduke | 2008-03-07 15:09:39 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown
+
+removing empty package
+
+------------------------------------------------------------------------
+r16643 | gswduke | 2008-03-07 15:09:20 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/RemoteUserAuthenticator.java
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/ShibAuthConfigLoader.java
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/ShibAuthConfiguration.java
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/ShibAuthConstants.java
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/StringUtil.java
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/RemoteUserAuthenticator.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/RemoteUserAuthenticator.java:16641)
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfigLoader.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/ShibAuthConfigLoader.java:16641)
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConfiguration.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/ShibAuthConfiguration.java:16641)
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/ShibAuthConstants.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/ShibAuthConstants.java:16641)
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication/shibboleth/StringUtil.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/StringUtil.java:16641)
+
+moved classes into shibauth/confluence/authentication/shibboleth package
+
+------------------------------------------------------------------------
+r16642 | gswduke | 2008-03-07 15:07:41 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/confluence/authentication
+
+added confluence/authentication part of package name
+
+------------------------------------------------------------------------
+r16641 | gswduke | 2008-03-07 15:05:59 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/edu
+
+removing edu package
+
+------------------------------------------------------------------------
+r16640 | gswduke | 2008-03-07 15:04:38 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/RemoteUserAuthenticator.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/RemoteUserAuthenticator.java:16639)
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/ShibAuthConfigLoader.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/ShibAuthConfigLoader.java:16639)
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/ShibAuthConfiguration.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/ShibAuthConfiguration.java:16639)
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/ShibAuthConstants.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/ShibAuthConstants.java:16639)
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/StringUtil.java (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/StringUtil.java:16639)
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/RemoteUserAuthenticator.java
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/ShibAuthConfigLoader.java
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/ShibAuthConfiguration.java
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/ShibAuthConstants.java
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth/georgetown/middleware/confluence/StringUtil.java
+
+moving files into new package shibauth
+
+------------------------------------------------------------------------
+r16639 | gswduke | 2008-03-07 15:03:25 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/shibauth (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/edu:16638)
+
+added shibauth package
+
+------------------------------------------------------------------------
+r16638 | gswduke | 2008-03-07 15:01:39 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/main/edu
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/java/edu (from /contrib/confluence/shibboleth-authenticator/trunk/src/main/edu:16637)
+
+added java and moved source under it
+
+------------------------------------------------------------------------
+r16637 | gswduke | 2008-03-07 15:01:12 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   D /contrib/confluence/shibboleth-authenticator/trunk/src/edu
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main/edu (from /contrib/confluence/shibboleth-authenticator/trunk/src/edu:16630)
+
+moving source under main
+
+------------------------------------------------------------------------
+r16636 | gswduke | 2008-03-07 15:00:50 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/main
+
+added main
+
+------------------------------------------------------------------------
+r16635 | gswduke | 2008-03-07 14:34:49 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   D /contrib/confluence/shibboleth-authenticator/trunk/build
+
+no idea how the build dir got checked in. removing it
+
+------------------------------------------------------------------------
+r16631 | gswduke | 2008-03-07 11:56:00 -0500 (Fri, 07 Mar 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/build.xml
+
+updating version to 1.3
+
+------------------------------------------------------------------------
+r14378 | gswduke | 2008-01-16 11:44:27 -0500 (Wed, 16 Jan 2008) | 4 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/build/edu/georgetown/middleware/confluence/RemoteUserAuthenticator.class
+   M /contrib/confluence/shibboleth-authenticator/trunk/build.xml
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware/confluence/RemoteUserAuthenticator.java
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware/confluence/ShibAuthConfigLoader.java
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware/confluence/ShibAuthConfiguration.java
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware/confluence/ShibAuthConstants.java
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware/confluence/StringUtil.java
+
+Refactored config loading, constants, method to split string by comma/semi-colon, and configuration VO. Changed comments in properties file to indicate "roles" are "groups" and worked a little on the comments in the config file. Changed version to 1.2 in build file.
+
+Needs to be tested.
+
+------------------------------------------------------------------------
+r14283 | bliong | 2008-01-13 21:25:53 -0500 (Sun, 13 Jan 2008) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware/confluence/RemoteUserAuthenticator.java
+
+updated files to contain role mapping and small refactoring of previous code.
+read remoteUserAuthenticator.properties for options available to do role/group assignment
+------------------------------------------------------------------------
+r13330 | gswduke | 2007-12-10 15:50:42 -0500 (Mon, 10 Dec 2007) | 2 lines
+Changed paths:
+   D /contrib/confluence/shibboleth-authenticator/trunk/dist
+
+removing accidentally checked in dist dir
+
+------------------------------------------------------------------------
+r13326 | gswduke | 2007-12-10 15:47:08 -0500 (Mon, 10 Dec 2007) | 2 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/trunk/test
+   A /contrib/confluence/shibboleth-authenticator/trunk/test/edu
+   A /contrib/confluence/shibboleth-authenticator/trunk/test/edu/georgetown
+   A /contrib/confluence/shibboleth-authenticator/trunk/test/edu/georgetown/middleware
+   A /contrib/confluence/shibboleth-authenticator/trunk/test/edu/georgetown/middleware/confluence
+   A /contrib/confluence/shibboleth-authenticator/trunk/test/edu/georgetown/middleware/confluence/RemoteAuthenticatorTest.java
+
+added test dir
+
+------------------------------------------------------------------------
+r13323 | gswduke | 2007-12-10 15:44:38 -0500 (Mon, 10 Dec 2007) | 2 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/trunk/TAGGING
+
+added doc on tagging
+
+------------------------------------------------------------------------
+r13322 | gswduke | 2007-12-10 15:41:47 -0500 (Mon, 10 Dec 2007) | 2 lines
+Changed paths:
+   M /contrib/confluence/shibboleth-authenticator/trunk/build/edu/georgetown/middleware/confluence/RemoteUserAuthenticator.class
+   D /contrib/confluence/shibboleth-authenticator/trunk/build-lib/atlassian-bucket-2006.03.14.branch.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/atlassian-bucket-2007.01.23.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/atlassian-spring-0.4.jar
+   D /contrib/confluence/shibboleth-authenticator/trunk/build-lib/atlassian-user-0.6.0e.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/atlassian-user-2007-05-22.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/commons-logging-1.0.4.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/confluence-2.4.5.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/junit.jar
+   D /contrib/confluence/shibboleth-authenticator/trunk/build-lib/log4j-1.2.12.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/log4j-1.2.8.jar
+   D /contrib/confluence/shibboleth-authenticator/trunk/build-lib/seraph-0.7.13.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/seraph-0.7.23.jar
+   R /contrib/confluence/shibboleth-authenticator/trunk/build-lib/servlet-api.jar
+   M /contrib/confluence/shibboleth-authenticator/trunk/build.xml
+   M /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware/confluence/RemoteUserAuthenticator.java
+
+implementing changes made by Duke University to make the authenticator work with more recent versions of Confluence
+
+------------------------------------------------------------------------
+r13321 | gswduke | 2007-12-10 15:05:29 -0500 (Mon, 10 Dec 2007) | 48 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/trunk/build
+   A /contrib/confluence/shibboleth-authenticator/trunk/build/edu
+   A /contrib/confluence/shibboleth-authenticator/trunk/build/edu/georgetown
+   A /contrib/confluence/shibboleth-authenticator/trunk/build/edu/georgetown/middleware
+   A /contrib/confluence/shibboleth-authenticator/trunk/build/edu/georgetown/middleware/confluence
+   A /contrib/confluence/shibboleth-authenticator/trunk/build/edu/georgetown/middleware/confluence/RemoteUserAuthenticator.class
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/atlassian-bucket-2006.03.14.branch.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/atlassian-user-0.6.0e.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/log4j-1.2.12.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/seraph-0.7.13.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build-lib/servlet-api.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/build.xml
+   A /contrib/confluence/shibboleth-authenticator/trunk/conf
+   A /contrib/confluence/shibboleth-authenticator/trunk/conf/remoteUserAuthenticator.properties
+   A /contrib/confluence/shibboleth-authenticator/trunk/dist
+   A /contrib/confluence/shibboleth-authenticator/trunk/dist/remoteUserAuth-1.0.jar
+   A /contrib/confluence/shibboleth-authenticator/trunk/src
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/edu
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware/confluence
+   A /contrib/confluence/shibboleth-authenticator/trunk/src/edu/georgetown/middleware/confluence/RemoteUserAuthenticator.java
+
+moving version 1.0 (from Georgetown's svn at https://svn.middleware.georgetown.edu/confluence/remoteAuthn/ ) into trunk with permission of Chad LaJoie of Georgetown. this is verbatim what is in the 1.0 tag:
+
+-------- Original Message --------
+Subject: 	Re: Latest Shibboleth authenticator plugin?
+Date: 	Wed, 05 Dec 2007 09:59:17 -0800
+From: 	Chad La Joie <chad.lajoie at switch.ch>
+Organization: 	SWITCH
+To: 	Gary Weaver <gary.weaver at duke.edu>
+CC: 	Michael R. Gettes <gettes at internet2.edu>, Scott Cantor <cantor.2 at osu.edu>
+References: 	<005401c836c2$f75fdfb0$e61f9f10$%2 at osu.edu> <4756CBAC.1080201 at duke.edu> <009201c8375b$cbe6a0a0$63b3e1e0$@2 at osu.edu> <1C1F3B3F-7F86-47B9-8E8B-88FC986FA87A at internet2.edu> <4756D773.9080109 at duke.edu>
+
+
+Sounds fine to me.  Go for it.
+
+The license on all my work is Apache 2.
+
+Gary Weaver wrote:
+> Do you guys mind if I move the most recent code (without the 
+> DukeStripper hack) in under Atlassian contrib's svn instance then and 
+> then we can just change the ShibbolizedConfluence page to point at it 
+> instead?
+> 
+> I could copy the older Georgetown version of the code into their 
+> subversion also and make the older jar available for older versions of 
+> confluence.
+> 
+> Chad, I'd definitely leave you (and anyone else that helped) on the 
+> author list, as the changes I made were mostly just to use the newer API 
+> and do some work to logging and exception handling (and refactorish kind 
+> of work- moving methods around and changing little things). But I can 
+> omit your new contact info if you don't want to be contacted (or if 
+> you'd rather provide gmail or some other long-lasting address just let 
+> me know).
+> 
+> I could get the code into atlassian subversion super-quick (I have the 
+> root of confluence under contrib checked out so I can easily add 
+> projects, and I can request a new plugin.
+> 
+> I don't remember what the license was for the Georgetown code. Any 
+> suggestions for what to use for the license? (I have been using BSD-style.)
+> 
+> Am I ok doing this?
+> 
+> Thanks,
+> 
+> Gary
+
+
+------------------------------------------------------------------------
+r13317 | gswduke | 2007-12-10 14:52:35 -0500 (Mon, 10 Dec 2007) | 2 lines
+Changed paths:
+   A /contrib/confluence/shibboleth-authenticator/branches
+   A /contrib/confluence/shibboleth-authenticator/tags
+   A /contrib/confluence/shibboleth-authenticator/trunk
+
+added trunk, branches, tags
+
+------------------------------------------------------------------------
diff --git a/history/build.xml.old b/history/build.xml.old
new file mode 100644
index 0000000..4c26251
--- /dev/null
+++ b/history/build.xml.old
@@ -0,0 +1,92 @@
+
+<!--
+*** THIS IS NO LONGER USED - IS JUST HERE FOR REFERENCE
+-->
+
+
+<project name="Confluence Remote User Authenticator" basedir="." default="package">
+
+	<property name="lib.dir" value="${basedir}/lib" />
+	<property name="src.dir" value="${basedir}/src" />
+	<property name="build-lib.dir" value="${basedir}/build-lib" />
+	<property name="build.dir" value="${basedir}/build" />
+	<property name="dist.dir" value="${basedir}/dist" />
+
+        <property name="test.classes.directory" value="${basedir}/test-classes" />
+
+	<property name="version" value="1.3" />
+	<property name="jar.name" value="remoteUserAuth-${version}.jar" />
+
+	<path id="build-path">
+		<fileset dir="${build-lib.dir}" includes="**/*.jar" />
+	</path>
+
+	<!-- Prepare directory structure for build -->
+	<target name="build-init">
+		<mkdir dir="${build.dir}" />
+	</target>
+
+	<!-- Compile core (non-test) classes -->
+	<target name="compile" depends="build-init">
+		<mkdir dir="${build.dir}" />
+
+		<javac srcdir="${src.dir}" destdir="${build.dir}" includes="**/*.java" debug="on" source="1.4">
+			<classpath refid="build-path" />
+		</javac>
+	</target>
+
+    <!-- Run Unit Tests -->
+    <target name="test" depends="compile">
+
+        <mkdir dir="${test.classes.directory}"/>
+
+        <javac destdir="${test.classes.directory}" debug="on" fork="yes">
+            <classpath refid="build-path"/>
+            <classpath path="${basedir}/build"/>
+            <src path="test"/>
+        </javac>
+
+        <copy todir="${test.classes.directory}">
+            <fileset dir="${basedir}/test">
+                <patternset includes="**/*.xml"/>
+                <patternset includes="**/*.properties"/>
+            </fileset>
+        </copy>
+
+        <junit fork="yes" haltonfailure="on">
+            <formatter type="plain" usefile="false"/>
+            <classpath refid="build-path"/>
+            <classpath path="${basedir}/build"/>
+            <classpath>
+                <dirset dir="${test.classes.directory}">
+                    <include name="**/"/>
+                </dirset>
+            </classpath>
+            <!--  to pick up the spring app context, if any -->
+            <batchtest todir="${test.classes.directory}">
+                <fileset dir="${test.classes.directory}">
+                    <include name="**/*Test.class"/>
+                    <exclude name="**/Abstract*.class"/>
+                </fileset>
+            </batchtest>
+        </junit>
+
+        <!--        <delete  dir="${test.classes.directory}"/>  -->
+    </target>
+
+	<!-- Create JAR file -->
+	<target name="package" depends="compile, test">
+		<mkdir dir="${dist.dir}" />
+
+		<jar destfile="${dist.dir}/${jar.name}" basedir="${build.dir}" whenempty="fail">
+			<zipfileset file="conf/remoteUserAuthenticator.properties" />
+		</jar>
+	</target>
+
+	<!-- Removes all build generated content -->
+	<target name="clean">
+		<delete dir="${build.dir}" />
+		<delete dir="${dist.dir}" />
+	</target>
+
+</project>
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..3a10715
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,105 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+         
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>net.shibboleth.tool.jira</groupId>
+    <artifactId>remoteUserAuth</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <name>Jira Shibboleth Authenticator</name>
+    <description>An Atlassian Jira seraph authenticator that works with Shibboleth</description>
+    <packaging>jar</packaging>
+    <repositories>
+        <repository>
+            <id>Atlassian Public</id>
+            <url>https://maven.atlassian.com/content/repositories/atlassian-public/</url>
+        </repository>
+    </repositories>
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.3</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.6</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.jira</groupId>
+            <artifactId>jira-core</artifactId>
+            <version>5.1</version>
+            <scope>provided</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>jndi</groupId>
+                    <artifactId>jndi</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>jta</groupId>
+                    <artifactId>jta</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>commons-logging</groupId>
+            <artifactId>commons-logging</artifactId>
+            <version>1.1</version>
+            <scope>provided</scope>
+            <exclusions>
+                <!-- Bamboo build complains about log4j not being in there... not sure why
+                <exclusion>
+                    <groupId>log4j</groupId>
+                    <artifactId>log4j</artifactId>
+                </exclusion>
+                -->
+                <exclusion>
+                    <groupId>logkit</groupId>
+                    <artifactId>logkit</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>avalon-framework</groupId>
+                    <artifactId>avalon-framework</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javax.servlet</groupId>
+                    <artifactId>servlet-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.atlassian.confluence.plugin</groupId>
+            <artifactId>func-test</artifactId>
+            <version>2.3</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.sourceforge.jwebunit</groupId>
+            <artifactId>jwebunit-htmlunit-plugin</artifactId>
+            <version>2.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.sourceforge.nekohtml</groupId>
+            <artifactId>nekohtml</artifactId>
+            <version>1.9.12</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+    <build>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-compiler-plugin</artifactId>
+          <version>2.5.1</version>
+          <configuration>
+            <source>1.6</source>
+            <target>1.6</target>
+          </configuration>
+        </plugin>
+      </plugins>
+    </build>
+</project>
diff --git a/src/main/java/net/shibboleth/tools/jira/authn/GroupMapper.java b/src/main/java/net/shibboleth/tools/jira/authn/GroupMapper.java
new file mode 100644
index 0000000..bc1c466
--- /dev/null
+++ b/src/main/java/net/shibboleth/tools/jira/authn/GroupMapper.java
@@ -0,0 +1,232 @@
+/*
+ Copyright (c) 2008-2012, Shibboleth Authenticator for Confluence Team
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ * Neither the name of the Shibboleth Authenticator for Confluence Team
+   nor the names of its contributors may be used to endorse or promote
+   products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package net.shibboleth.tools.jira.authn;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * GroupMapper is capable to process an incoming value(s) and
+ * producing the output desired as specified by the mapping regex.
+ * The properties that can be specified in this class:
+ * <ul>
+ * <li>match = the regex to which the incoming header to be matched</li>
+ * <li>transform = the regex that is responsible to produce the expected output
+ * once "match" is successful</li>
+ * <li>casesensitive= boolean value whether to be sensitive about matching
+ * the input. default is true</li>
+ * <li>retract = boolean value whether this mapping should remove a particular
+ * group from user. default is false</li>
+ * <li>force = boolean value to decide whether this mapper will override any
+ * other mappers' result (with respect to the same group). default is false</li>
+ * </ul>
+ * <p/>
+ * The processing logic is as follow:
+ * <ol>
+ * <li>an input such as "urn:abc:xyz" which represents a group is evaluated</li>
+ * <li>supposed match regex = urn\:(abc)\:(.*)</li>
+ * <li>supposed transform = $1_$2</li>
+ * <li>initially the groupmapper will check whether the input satisfies
+ * its 'match' regex, if so then this mapper is activated</li>
+ * <li>it then check whether a specific transform is defined, if so
+ * the transform is executed (if it is not explicitly defined, the output
+ * will be exactly whatever the outcome of match regex which is the same as
+ * the input</li>
+ * <li>if retract is set to true, then this mapper will perform reversal of
+ * previously made mapping (e.g. it will remove the user from confluence's
+ * group matching the output rather than adding the user to the group;
+ * purging the roles). This only <strong>happens</strong> when there
+ * is <strong>no</strong> other mappers explicitly requiring the user
+ * to be added to the group (e.g. allow-overide combining algorithm)
+ * and none other mappers that process the same output has explicitly
+ * specify its "force" attribute to be true.</li>
+ * <li>if force is true, then the outcome of this mapper takes precedence</li>
+ * </ol>
+ * <p/>
+ * Two or more mappers that produce the same output (group) and have their "force"
+ * attribute specified to be true will have undefined result (1 of these
+ * mappers will win, but dont know which one ;)
+ * <p/>
+ * This mapper, when placed in sorted list, will be placed at the back of the
+ * list if its 'force' attribute is set to true.
+ */
+public class GroupMapper {
+    private String matchRegex;
+    private String transform;
+    private String name;
+    private boolean sensitive = true;
+    private final static Log log = LogFactory.getLog(GroupMapper.class);
+
+    /**
+     * Make a new group mapper processor. Pay attention to the inputs as
+     * they carry specific meanings on null or empty strings.
+     *
+     * @param name      name of this mapper, this could be any labels
+     * @param match     regex to do the matching of inputs, leave as null to match
+     *                  against any inputs (e.g. always return positive match)
+     * @param transform to transform and produce the output, leave as null
+     *                  if this mapper should only use exact string in the input (provided the
+     *                  match is successful). this has passthrough effect for matched regex.
+     * @param sensitive should the matching be case sensitive
+     */
+    public GroupMapper(String name, String match, String transform, boolean sensitive) {
+        this.name = name;
+        setMatchRegex(match);
+        setTransform(transform);
+        this.sensitive = sensitive;
+    }
+
+    public boolean isCaseSensitive() {
+        return sensitive;
+    }
+
+    public void setCaseSensitive(boolean sensitive) {
+        this.sensitive = sensitive;
+    }
+
+    /**
+     * Go through the following processing logic:
+     * <ul>
+     * <li>'match' logic: check if the input matches the 'match' regex, if
+     * so continue perform the regex groupings (if exist) and continue to
+     * 'transform' logic</li>
+     * <li>if 'match' regex is null but 'transform' exists, continue to
+     * transform logic</li>
+     * <li>'transform' logic: if transform is null, then return the
+     * value, otherwise perform value transformation depend on the regex specified
+     * in 'transform'</li>
+     * </ul>
+     * <p/>
+     * Transform string can be separated by comma to indicate
+     * multiple groups in confluence, e.g. $1, $3 means anything
+     * that matches 'match' regex will cause group 1 and 3 becoming
+     * confluence groups.
+     *
+     * @param initValue if this value is null or empty, then only
+     *                  GroupManager <strong>without</strong> 'match' regex is executed. if
+     *                  transform is also null, then it simply return null output
+     * @return the final group output value. if there are multiple groups
+     *         they are separated by comma or semicolon (provided match regex or
+     *         transform has specified the comma/semicolon
+     */
+    public String process(String initValue) {
+        String value = initValue;
+        if (value == null) {
+            value = "";
+        }
+
+        if (value.length() == 0) {
+            if (matchRegex == null && transform == null) {
+                return null;
+            }
+        }
+
+        String regex = matchRegex;
+        //accept any input
+        if (matchRegex == null) {
+            regex = ".*";
+        }
+
+        //perform matches first
+        Pattern p = null;
+        if (isCaseSensitive()) {
+            p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
+        } else {
+            p = Pattern.compile(regex);
+        }
+
+        Matcher m = p.matcher(value);
+        if (!m.matches()) {
+            // this has been helpful for users that are debugging their regexp
+            if (log.isDebugEnabled()) {
+                log.debug("Regexp '" + regex + "' did not match value='" + value + "'");
+            }
+            return null;
+        }
+
+        //perform transformation replacements
+        //TODO: any better way of doing this??
+        String t = transform;
+        if (t == null) {
+            t = value;
+        }
+
+        for (int i = m.groupCount(); i > 0; i--) {
+            if (log.isDebugEnabled()) {
+                log.debug("The group identified $" + i + "=" + m.group(i));
+            }
+
+            t = t.replaceAll("\\$" + i, m.group(i));
+        }
+
+        //in case someone uses $0
+        t = t.replaceAll("\\$0", m.group(0));
+
+        if (log.isDebugEnabled()) {
+            log.debug("Converted: value=" + value + " to group=" + t);
+        }
+
+        return t;
+    }
+
+    /**
+     * Simply return the label/name of this group mapper
+     */
+    public String toString() {
+        return name;
+    }
+
+    public String getMatchRegex() {
+        return matchRegex;
+    }
+
+    public String getTransform() {
+        return transform;
+    }
+
+    public void setMatchRegex(String regex) {
+        if (regex != null && regex.trim().length() == 0) {
+            regex = null;
+        }
+
+        matchRegex = regex;
+    }
+
+    public void setTransform(String transform) {
+        if (transform != null && transform.trim().length() == 0) {
+            transform = null;
+        }
+
+        this.transform = transform;
+    }
+}
diff --git a/src/main/java/net/shibboleth/tools/jira/authn/RemoteUserAuthenticator.java b/src/main/java/net/shibboleth/tools/jira/authn/RemoteUserAuthenticator.java
new file mode 100644
index 0000000..41b6dd4
--- /dev/null
+++ b/src/main/java/net/shibboleth/tools/jira/authn/RemoteUserAuthenticator.java
@@ -0,0 +1,1317 @@
+/*
+ Copyright (c) 2008-2012, Shibboleth Authenticator for Confluence Team
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ * Neither the name of the Shibboleth Authenticator for Confluence Team
+   nor the names of its contributors may be used to endorse or promote
+   products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * See source control logs and revision history for further detail of changes.
+ * Modified 2009-09-29 call super.login() if REMOTE_USER wasn't set to enable local Confluence login (SHBL-24) [Juhani Gurney]
+ * Modified 2009-01-22 to make use of ShibLoginFilter (SHBL-16), make updateLastLogin as optional [Bruc Liong]
+ * Modified 2009-01-05 to revamp the mapping processing mechanism to handle regex, purging roles, etc (SHBL-6) [Bruc Liong]
+ * Modified 2008-12-03 to encorporate patch from Vladimir Mencl for SHBL-8 related to CONF-12158 (DefaultUserAccessor checks permissions before adding membership in 2.7 and later)
+ * Modified 2008-07-29 to fix UTF-8 encoding [Helsinki University], made UTF-8 fix optional [Duke University]
+ * Modified 2008-01-07 to add role mapping from shibboleth attribute (role) to confluence group membership. [Macquarie University - MELCOE - MAMS], refactor config loading, constants, utility method, and added configuration VO [Duke University]
+ * Modified 2007-05-21 additional checks/logging and some small refactoring. Changed to use UserAccessor so should work with Confluence 2.3+ [Duke University]
+ * Original version by Georgetown University. Original version (v1.0) from: https://svn.middleware.georgetown.edu/confluence/remoteAuthn
+ */
+
+package net.shibboleth.tools.jira.authn;
+
+//import com.atlassian.confluence.event.events.security.LoginEvent;
+//import com.atlassian.confluence.event.events.security.LoginFailedEvent;
+
+import com.atlassian.crowd.embedded.api.CrowdService;
+import com.atlassian.crowd.embedded.api.Group;
+import com.atlassian.crowd.embedded.api.User;
+import com.atlassian.crowd.embedded.impl.ImmutableUser;
+import com.atlassian.jira.component.ComponentAccessor;
+import com.atlassian.jira.security.groups.GroupManager;
+import com.atlassian.jira.security.login.JiraSeraphAuthenticator;
+import com.atlassian.jira.security.login.LoginManager;
+import com.atlassian.jira.user.util.UserManager;
+import com.atlassian.seraph.auth.AuthenticatorException;
+import com.atlassian.seraph.auth.LoginReason;
+import com.atlassian.spring.container.ContainerManager;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import javax.servlet.ServletRequestWrapper;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.File;
+import java.security.Principal;
+import java.security.SecureRandom;
+import java.util.*;
+
+//~--- JDK imports ------------------------------------------------------------
+
+/**
+ * An authenticator that uses the REMOTE_USER header as proof of authentication.
+ * <p/>
+ * Configuration properties are looked for in
+ * <i>/remoteUserAuthenticator.properties</i> on the classpath. This file
+ * may contain the following properties:
+ * <ul>
+ * <li><strong>convert.to.utf8</strong> - Convert all incoming header values to UTF-8</li>
+ * <li><strong>create.users</strong> - Indicates whether accounts should be
+ * created for individuals the first they are encountered
+ * (acceptable values: true/false)</li>
+ * <li><strong>update.info</strong> - Indicates whether existing accounts
+ * should have their name and email address information
+ * updated when the user logs in (acceptable values: true/false)</li>
+ * <li><strong>default.roles</strong> - The default roles newly created
+ * accounts will be given (format: comma seperated list)</li>
+ * <li><strong>purge.roles</strong> - Roles to be purged automatically of users
+ * who don't have attributes to regain membership anymore (comma/semicolon
+ * separated regex)</li>
+ * <li><strong>reload.config</strong> - Automatically reload config when
+ * change</li>
+ * <li><strong>header.fullname</strong> - The name of the HTTP header that
+ * will carry the full name of the user</li>
+ * <li><strong>header.email</strong> - The name of the HTTP header that will
+ * carry the email address for the user</li>
+ * <li><strong>header.remote_user</strong> - The name of the HTTP header that will
+ * carry the username</li>
+ * <p/>
+ * <li><strong>username.convertcase</strong> - Indicates whether usernames
+ * should be converted to lowercase before use</li>
+ * <p/>
+ * <li><strong>update.roles</strong> - Indicates whether the existing accounts
+ * should have their roles updated based on the header information. note: old
+ * roles are not removed if the header doesn't contain it. (Acceptable values:
+ * true/false. Default to false)</li>
+ * <p/>
+ * <li><strong>dynamicroles.auto_create_role</strong> - should new roles be
+ * automatically created in confluence (and users assigned to it). Default to false
+ * <p/>
+ * <li><strong>dynamicroles.header.XXX</strong> - XXX is the name of the
+ * HTTP header that will carry user's role information. Lists the mapper
+ * names that are supposed to handle these roles. Mapper labels separated by
+ * comma or semicolon. If this entry is empty or not existing, then no dynamic
+ * role mapping loaded for this particular header. Example:
+ * dynamicroles.header.SHIB-EP-ENTITLEMENT = mapper1, label5</li>
+ * <li><strong>dynamicroles.mapper.YYY </strong> - YYY is the label name for the
+ * mapper. This mapper is responsible of matching the input and processing
+ * value transformation on the input. The output of the mapper is the role
+ * supplied to confluence.See further examples in properties
+ * file for details.
+ * <ul><li><strong>match</strong> - regex for the mapper to match against
+ * the given input</li>
+ * <li><strong>casesensitive</strong> - should the matching performed by 'match'
+ * be case sensitive. Default to true</li>
+ * <li><strong>transform</strong> - a fix string replacement of the input
+ * (e.g. the group or groups). when not specified, it will simply takes the
+ * input value. roles as the result of matching input (separated by comma or
+ * semicolon). parts of initial input can be used here in the form
+ * of $0, $1...$N where $0 represents the whole input string, $1...N represent
+ * regex groupings as used in 'match' regex</li>
+ * </ul>
+ * Example: <br/>
+ * dynamicroles.mapper.label5.match = some\:example\:(.+)\:role-(.*) <br/>
+ * dynamicroles.mapper.label5.transform = $1, $2, confluence-$2
+ * </li>
+ * </ul>
+ */
+public class RemoteUserAuthenticator extends JiraSeraphAuthenticator {
+
+    private static final long serialVersionUID = -5608187140008286796L;
+    private final static Log log = LogFactory.getLog(RemoteUserAuthenticator.class);
+    private SecureRandom prng = new SecureRandom();
+    private static ShibAuthConfiguration config;
+
+    // Initialize properties from property file
+    static {
+        config = ShibAuthConfigLoader.getShibAuthConfiguration(null);
+    }
+
+    public RemoteUserAuthenticator() {
+        // SHBL-48/CONF-22266 - Authenticators in Confluence 3.5:
+        // * Cannot have Atlassian beans injected via Spring (see comment late in CONF-22266)
+        // * Authenticators must be classloaded and cannot be Atlassian plugins v1 or v2 (see comment late in
+        // CONF-22266), so neither setter nor constructor injection of CrowdService instance would work.
+        // * Can only get bean instances using ContainerManager after the beans have been constructed, so cannot be done
+        // here in constructor.
+    }
+
+    /**
+     * Check if the configuration file should be reloaded and reload the configuration.
+     */
+    private void checkReloadConfig() {
+
+        if (config.isReloadConfig() && (config.getConfigFile() != null)) {
+            if (System.currentTimeMillis() < config.getConfigFileLastChecked() +
+                    config.getReloadConfigCheckInterval()) {
+                return;
+            }
+
+            long configFileLastModified = new File(config.getConfigFile()).lastModified();
+
+            if (configFileLastModified != config.getConfigFileLastModified()) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Config file has been changed, reloading");
+                }
+
+                config = ShibAuthConfigLoader.getShibAuthConfiguration(config);
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug("Config file has not been changed, not reloading");
+                }
+
+                config.setConfigFileLastChecked(System.currentTimeMillis());
+            }
+        }
+    }
+
+    /**
+     * Assigns a user to the roles.
+     *
+     * @param user the user to assign to the roles.
+     */
+    private void assignUserToRoles(Principal user, Collection roles, CrowdService crowdService, User crowdUser) {
+        if (user == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("User was null, not adding any roles...");
+            }
+        } else if (roles.size() == 0) {
+            if (log.isDebugEnabled()) {
+                log.debug("No roles specified, not adding any roles...");
+            }
+        } else {
+            GroupManager groupManager = getGroupManager();
+            if (groupManager == null) {
+                throw new RuntimeException("groupManager was not wired in RemoteUserAuthenticator");
+            }
+
+            for (Iterator it = roles.iterator(); it.hasNext(); ) {
+                String role = it.next().toString().trim();
+
+                if (role.length() == 0) {
+                    continue;
+                }
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Assigning " + user.getName() + " to role " + role);
+                }
+
+                Group group = crowdService.getGroup(role);
+                if (group == null) {
+                    if (config.isAutoCreateGroup()) {
+                        try {
+                            if (log.isDebugEnabled()) {
+                                log.debug("Creating missing role '" + role + "'.");
+                            }
+                            groupManager.createGroup(role);
+                            group = crowdService.getGroup(role);
+                        } catch (Throwable t) {
+                            log.error("Cannot create role '" + role + "'.", t);
+                            continue;
+                        }
+                    } else {
+                        if (log.isDebugEnabled()) {
+                            log.debug("Skipping autocreation of role '" + role + "'.");
+                        }
+
+                        continue; //no point of attempting to allocate user
+                    }
+                }
+
+                if (crowdUser == null) {
+                    log.warn("Could not find user '" + user.getName() + "' to add them to role '" + role + "'.");
+                } else if (!crowdUser.isActive()) {
+                    log.warn("User '" + user.getName() + "' was inactive, so did not add them to role '" + role + "'.");
+                } else if (group == null) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Skipping " + user.getName() + " to role " + role + ", because crowdService.getGroup(\"" + role + "\") returned null.");
+                    }
+                } else if (crowdService.isUserMemberOfGroup(crowdUser, group)) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Skipping " + user.getName() + " to role " + role + " - already a member");
+                    }
+
+                } else {
+                    try {
+                        addUserToGroup(crowdService, crowdUser, group);
+                    } catch (Throwable t) {
+                        log.error("Failed to add user " + user + " to role " + role + ".", t);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Purge user from roles it no longer should have (based on current Shibboleth attributes).
+     * Remove the user from all roles listed in purgeRoles that are not
+     * included in the current list of roles the user would get assigned to
+     * based on the Shibboleth attributes received.
+     *
+     * @param user        the user to assign to the roles.
+     * @param rolesToKeep keep these roles, otherwise everything else
+     *                    mentioned in the purgeMappings can go.
+     */
+    private void purgeUserRoles(Principal user, Collection rolesToKeep) {
+        if ((config.getPurgeMappings().size() == 0)) {
+            if (log.isDebugEnabled()) {
+                log.debug("No roles to purge specified, not purging any roles...");
+            }
+        } else {
+            GroupManager groupManager = getGroupManager();
+            if (groupManager == null) {
+                throw new RuntimeException("groupManager was not wired in RemoteUserAuthenticator");
+            }
+
+            CrowdService crowdService = getCrowdService();
+            if (crowdService == null) {
+                throw new RuntimeException("crowdService was not wired in RemoteUserAuthenticator");
+            }
+
+            User crowdUser = crowdService.getUser(user.getName());
+            Collection purgeMappers = config.getPurgeMappings();
+
+            Collection<String> roles = groupManager.getGroupNamesForUser(crowdUser);
+            Iterator<String> i = roles.iterator();
+            while (i.hasNext()) {
+                String role = i.next();
+                if (!StringUtil.containsStringIgnoreCase(rolesToKeep, role)) {
+                    //run through the purgeMappers for this role
+                    for (Iterator it2 = purgeMappers.iterator(); it2.hasNext(); ) {
+                        GroupMapper mapper = (GroupMapper) it2.next();
+
+                        String output = mapper.process(role);
+                        if (output != null) {
+                            try {
+                                Group group = crowdService.getGroup(role);
+                                if (crowdService.isUserMemberOfGroup(crowdUser, group)) {
+                                    if (log.isDebugEnabled()) {
+                                        log.debug("Removing user " + user.getName() + " from role " + role);
+                                    }
+
+                                    removeUserFromGroup(crowdService, crowdUser, group);
+
+                                    // Only remove one group per login. Assuming this is to avoid massive delays in
+                                    // login for a user removed from a lot of groups.
+                                    break;
+                                }
+                            } catch (Throwable t) {
+                                log.error("Error encountered in removing user " + user.getName() + " from role " + role,
+                                        t);
+                            }
+                        }
+                    }
+                } else {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Keeping role " + role + " for user " + user.getName());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Change userid to lower case.
+     *
+     * @param userid userid to be changed
+     * @return lower case version of it
+     */
+    private String convertUsername(String userid) {
+        if (userid != null) {
+            userid = userid.toLowerCase();
+        }
+
+        return userid;
+    }
+
+    /**
+     * Creates a new user if the configuration allows it.
+     *
+     * @param username user name for the new user
+     * @return the new user
+     */
+    private void createUser(String username, String fullName, String email) {
+        if (log.isInfoEnabled()) {
+            log.info("Creating user account for " + username);
+        }
+
+        try {
+            createUser(getCrowdService(), username, fullName, email);
+        } catch (Throwable t) {
+
+            // Note: just catching EntityException like we used to do didn't
+            // seem to cover Confluence massive with Oracle
+            if (log.isDebugEnabled()) {
+                log.debug("Error creating user " + username +
+                        ". Will ignore and try to get the user (maybe it was already created)", t);
+            }
+        }
+    }
+
+    private void updateUser(Principal user, String fullName, String emailAddress) {
+        // If we have new values for name or email, update the user object
+        if (user == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("User is null, so can't update it.");
+            }
+        } else {
+            boolean updated = false;
+
+            CrowdService crowdService = getCrowdService();
+            if (crowdService == null) {
+                throw new RuntimeException("crowdService was not wired in RemoteUserAuthenticator");
+            }
+            User crowdUser = crowdService.getUser(user.getName());
+            ImmutableUser.Builder userBuilder = new ImmutableUser.Builder();
+            // Have to clone the user before making mods.
+            userBuilder.active(crowdUser.isActive());
+            userBuilder.directoryId(crowdUser.getDirectoryId());
+            userBuilder.displayName(crowdUser.getDisplayName());
+            userBuilder.emailAddress(crowdUser.getEmailAddress());
+            userBuilder.name(crowdUser.getName());
+
+            if ((fullName != null) && !fullName.equals(crowdUser.getDisplayName())) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Updating user fullName to '" + fullName + "'");
+                }
+
+                userBuilder.displayName(fullName);
+                updated = true;
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug("New user fullName is same as old one: '" + fullName + "'");
+                }
+            }
+
+            if ((emailAddress != null) && !emailAddress.equals(crowdUser.getEmailAddress())) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Updating user emailAddress to '" + emailAddress + "'");
+                }
+
+                userBuilder.emailAddress(emailAddress);
+                updated = true;
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug("New user emailAddress is same as old one: '" + emailAddress + "'");
+                }
+            }
+
+            if (updated) {
+                try {
+                    updateUser(crowdService, userBuilder.toUser());
+                } catch (Throwable t) {
+                    log.error("Couldn't update user " + user.getName(), t);
+                }
+            }
+        }
+    }
+
+    private String getLoggedInUser(HttpServletRequest request) {
+        String remoteUser = null;
+
+        if (config.getRemoteUserHeaderName() != null) {
+            String headerValue = request.getHeader(config.getRemoteUserHeaderName());
+            // the Shibboleth SP sends multiple values as single value, separated by comma or semicolon
+            List values = StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(headerValue);
+
+            if (values != null && values.size() > 0) {
+                // use the first in the list, if header is defined multiple times. Otherwise should call getHeaders().
+                remoteUser = (String) values.get(0);
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Got remoteUser '" + remoteUser + "' for header '" + config.getRemoteUserHeaderName() +
+                            "'");
+                }
+
+                if (config.isConvertToUTF8()) {
+                    String tmp = StringUtil.convertToUTF8(remoteUser);
+                    if (tmp != null) {
+                        remoteUser = tmp;
+                        if (log.isDebugEnabled()) {
+                            log.debug("remoteUser converted to UTF-8 '" + remoteUser + "' for header '" + config.
+                                    getRemoteUserHeaderName() + "'");
+                        }
+                    }
+                }
+            }
+
+        } else {
+            remoteUser = unwrapRequestIfNeeded(request).getRemoteUser();
+        }
+
+        return remoteUser;
+    }
+
+    // For SHBL-46 (Confluence 3.4.6 no longer wraps request- Thanks to Chad LaJoie for this fix!)
+    private HttpServletRequest unwrapRequestIfNeeded(HttpServletRequest request) {
+        if (request instanceof ServletRequestWrapper) {
+            return (HttpServletRequest) ((ServletRequestWrapper) request).getRequest();
+        }
+
+        return request;
+    }
+
+    private String getEmailAddress(HttpServletRequest request) {
+        String emailAddress = null;
+
+        if (config.getEmailHeaderName() != null) {
+            String headerValue = request.getHeader(config.getEmailHeaderName());
+            // The Shibboleth SP sends multiple values as single value, separated by comma or semicolon.
+            List values = StringUtil.
+                    toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(headerValue);
+
+            if (values != null && values.size() > 0) {
+                // Use the first email in the list.
+                emailAddress = (String) values.get(0);
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Got emailAddress '" + emailAddress + "' for header '" + config.getEmailHeaderName() +
+                            "'");
+                }
+
+                if (config.isConvertToUTF8()) {
+                    String tmp = StringUtil.convertToUTF8(emailAddress);
+                    if (tmp != null) {
+                        emailAddress = tmp;
+                        if (log.isDebugEnabled()) {
+                            log.debug("emailAddress converted to UTF-8 '" + emailAddress + "' for header '" +
+                                    config.getEmailHeaderName() + "'");
+                        }
+                    }
+                }
+            }
+
+            if ((emailAddress != null) && (emailAddress.length() > 0)) {
+                emailAddress = emailAddress.toLowerCase();
+            }
+        } else {
+            if (log.isDebugEnabled()) {
+                log.debug("User email address header name in config was null/not specified.");
+            }
+        }
+
+        return emailAddress;
+    }
+
+    private String getFullName(HttpServletRequest request, String userid) {
+        String fullName = null;
+
+        if (config.getFullNameHeaderName() != null) {
+            // assumes it is first value in list, if header is defined multiple times. Otherwise would need to call getHeaders()
+            String headerValue = request.getHeader(config.getFullNameHeaderName());
+            // the Shibboleth SP sends multiple values as single value, separated by comma or semicolon
+            List values = StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(headerValue);
+
+            if (values != null && values.size() > 0) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Original value of full name header '" + config.getFullNameHeaderName() + "' was '" +
+                            headerValue + "'");
+                }
+
+                // use the first full name in the list
+                //fullName = (String) values.get(1) + " " + (String) values.get(0);
+
+                if (config.getFullNameMappings() == null || config.getFullNameMappings().size() == 0) {
+                    // Default is to just use the first header value, if no fullname mappings.
+                    fullName = (String) values.get(0);
+                } else {
+                    fullName = createFullNameUsingMapping(headerValue, values);
+                }
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Got fullName '" + fullName + "' for header '" + config.getFullNameHeaderName() + "'.");
+                }
+
+                if (config.isConvertToUTF8()) {
+                    String tmp = StringUtil.convertToUTF8(fullName);
+                    if (tmp != null) {
+                        fullName = tmp;
+                        if (log.isDebugEnabled()) {
+                            log.debug("fullName converted to UTF-8 '" + fullName + "' for header '" +
+                                    config.getFullNameHeaderName() + "'.");
+                        }
+                    }
+                }
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug("User full name header name in config was null/not specified.");
+                }
+            }
+        }
+
+        if ((fullName == null) || (fullName.length() == 0)) {
+            if (log.isDebugEnabled()) {
+                log.debug("User full name was null or empty. Defaulting full name to user id.");
+            }
+
+            fullName = userid;
+        }
+
+        return fullName;
+    }
+
+    /**
+     * This will populate accumulated (containing all roles discovered).
+     */
+    private void getRolesFromHeader(HttpServletRequest request,
+                                    Set accumulatedRoles) {
+        Set attribHeaders = config.getGroupMappingKeys();
+
+        // check if we're interested in some headers
+        if (attribHeaders.isEmpty()) {
+            return;
+        }
+
+        // log headers (this is helpful to users for debugging what is sent in)
+        if (log.isDebugEnabled()) {
+            StringBuffer sb = new StringBuffer("HTTP Headers: ");
+            boolean concat = false;
+            for (Enumeration en = request.getHeaderNames(); en.hasMoreElements(); ) {
+                if (concat) {
+                    sb.append(", ");
+                }
+                String headerName = en.nextElement().toString();
+                sb.append("'" + headerName + "' = '" + request.getHeader(headerName) + "'");
+                concat = true;
+            }
+            log.debug(sb.toString());
+        }
+
+        //process the headers by looking up only those list of registered headers
+        for (Iterator headerIt = attribHeaders.iterator(); headerIt.hasNext(); ) {
+            String headerName = headerIt.next().toString();
+            for (Enumeration en = request.getHeaders(headerName); en.hasMoreElements(); ) {
+                String headerValue = en.nextElement().toString();
+
+                //shib sends values in semicolon separated, so split it up too
+                List headerValues = StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(headerValue);
+                for (int j = 0; j < headerValues.size(); j++) {
+                    headerValue = (String) headerValues.get(j);
+                    if (config.isConvertToUTF8()) {
+                        String tmp = StringUtil.convertToUTF8(headerValue);
+                        if (tmp != null) {
+                            headerValue = tmp;
+                        }
+                    }
+
+                    if (log.isDebugEnabled()) {
+                        log.debug("Processing dynamicroles header=" + headerName + ", value=" + headerValue);
+                    }
+
+                    Collection mappers = config.getGroupMappings(headerName);
+                    boolean found = false;
+
+                    for (Iterator mapperIt = mappers.iterator(); mapperIt.hasNext(); ) {
+                        GroupMapper mapper = (GroupMapper) mapperIt.next();
+
+                        // We may get multiple groups returned by a single matched, e.g. matching "XXX" --> "A, B, C".
+                        String[] results = (String[]) StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(
+                                mapper.process(headerValue)).toArray(new String[0]);
+
+                        for (int i = 0; i < results.length; i++) {
+                            String result = results[i];
+
+                            if (result.length() != 0) {
+                                if (!accumulatedRoles.contains(result)) {
+                                    if (config.isOutputToLowerCase()) {
+                                        result = result.toLowerCase();
+                                    }
+
+                                    accumulatedRoles.add(result);
+
+                                    if (log.isDebugEnabled()) {
+                                        log.debug("Found role mapping from '" + headerValue + "' to '" + result + "'");
+                                    }
+                                }
+                                found = true;
+                            }
+                        }
+                    }
+
+                    if (log.isDebugEnabled() && !found) {
+                        log.debug("No mapper capable of processing role value=" + headerValue);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * @see com.atlassian.confluence.user.ConfluenceAuthenticator#login(
+     *javax.servlet.http.HttpServletRequest,
+     *      javax.servlet.http.HttpServletResponse,
+     *      java.lang.String username,
+     *      java.lang.String password,
+     *      boolean cookie)
+     *      <p/>
+     *      Check if user has been authenticated by Shib. Username, password, and cookie are totally ignored.
+     */
+    public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie) throws AuthenticatorException {
+
+        // Converting reliance on getUser(request,response) to use login(...) instead. The logic flow is:
+        // 1) Seraph Login filter, which is based on username/password kicks in (declared at web.xml)
+        // 2) It bails out altogether and identified user as invalid (without calling any of login(request,response)
+        //    declared here.
+        // 3) Seraph Security filter kicks in (declared at web.xml)
+        // 4) It calls getUser(request,response) and assign roles to known user.
+        // Hence, getUser(request,response) will only be called from Seraph SecurityFilter. This authenticator can use
+        // ShibLoginFilter to make sure login is performed in some versions of Confluence, but it works without it, so
+        // that is off by default.
+
+        String remoteIP = request.getRemoteAddr();
+        String remoteHost = request.getRemoteHost();
+
+        if (log.isDebugEnabled()) {
+            log.debug("login(...) called. requestURL=" + request.getRequestURL() + ", username=" + username + ", remoteIP=" + remoteIP + ", remoteHost=" + remoteHost);
+        }
+
+        // Check if the user is already logged in
+        Principal user = null;
+        try {
+            user = getUserFromSession(request);
+            if (user != null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("" + user.getName() + " already logged in (user in session), returning.");
+                    log.debug("Authenticator is returning true from call to public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)");
+                }
+
+                return true;
+            } else if (log.isDebugEnabled()) {
+                log.debug("Didn't have a user in session.");
+            }
+        } catch (Throwable t) {
+            log.error("Got the following error attempting to get existing user from session.", t);
+        }
+
+        // Since they aren't logged in, get the user name from the configured header (e.g. REMOTE_USER).
+        String userid = createSafeUserid(getLoggedInUser(request));
+
+        if ((userid == null) || (userid.length() <= 0)) {
+            if (log.isDebugEnabled()) {
+                log.debug("Remote user was null or empty.");
+            }
+
+            // Calling super.login to try local login if username and password are set. Local login won't work if
+            // ShibLoginFilter is used
+            if (username != null && password != null) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Trying local login for user " + username);
+                }
+
+                boolean localLoginSuccess = super.login(request, response, username, password, cookie);
+                if (localLoginSuccess) {
+                    loginSuccessful(request, response, username, user, remoteHost, remoteIP);
+                }
+                else {
+                    loginFailed(request, username, remoteHost, remoteIP, "LocalUserLoginFailed");
+                }
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Authenticator is returning " + localLoginSuccess + " from call to public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)");
+                }
+
+                return localLoginSuccess;
+            }
+            else {
+                if (log.isDebugEnabled()) {
+                    log.debug("Cannot perform local login because username or password was not provided.");
+                }
+
+                loginFailed(request, username, remoteHost, remoteIP, "LocalUserLoginWithNoCredentials");
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Authenticator is returning false from call to public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)");
+                }
+
+                return false;
+            }
+        }
+
+        // Now that we know we will be trying to log the user in,
+        // let's see if we should reload the config file first
+        checkReloadConfig();
+
+        // Convert username to all lowercase because of issues with case, at least in earlier versions of Confluence.
+        if (config.isUsernameConvertCase()) {
+            userid = convertUsername(userid);
+        }
+
+        CrowdService crowdService = getCrowdService();
+        if (crowdService == null) {
+            loginFailed(request, username, remoteHost, remoteIP, "AuthenticatorConfigFailure");
+            if (log.isDebugEnabled()) {
+                log.debug("Authenticator is throwing RuntimeException from call to public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)");
+            }
+            throw new RuntimeException("crowdService was not wired in RemoteUserAuthenticator");
+        }
+
+        User crowdUser = null;
+        // ensure user is active
+        if (userid != null) {
+	        crowdUser = crowdService.getUser(userid);
+	        if (crowdUser != null && !crowdUser.isActive()) {
+	            log.info("Login failed for user '" + userid + "', because user is set as inactive. remoteIP=" + remoteIP + " remoteHost=" + remoteHost);
+	            loginFailed(request, username, remoteHost, remoteIP, "UserInactive");
+	
+	            if (log.isDebugEnabled()) {
+	                log.debug("Authenticator is returning false from call to public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)");
+	            }
+	            return false;
+	        }
+        }
+
+        // Pull name and address from headers
+        String fullName = getFullName(request, userid);
+        String emailAddress = getEmailAddress(request);
+
+        // Try to get the user's account based on the user name
+        user = getUser(userid);
+        boolean newUser = false;
+
+        // User didn't exist or was problem getting it. we'll try to create it if we can, otherwise will try to get it
+        // again.
+        if (user == null) {
+            if (config.isCreateUsers()) {
+                createUser(userid, fullName, emailAddress);
+    	        newUser = true;
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug("Configuration does NOT allow creation of new user accounts, authentication will fail for " +
+                            username);
+                }
+
+                loginFailed(request, username, remoteHost, remoteIP, "CreateUserDisabled");
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Authenticator is returning false from call to public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)");
+                }
+                return false;
+            }
+
+            user = getUser(userid);
+            if (user != null) {
+                crowdUser = crowdService.getUser(userid);
+    	        if (crowdUser != null && !crowdUser.isActive()) {
+    	            log.info("Login failed for user '" + userid + "', because user is set as inactive. remoteIP=" + remoteIP + " remoteHost=" + remoteHost);
+    	            loginFailed(request, username, remoteHost, remoteIP, "UserInactive");
+    	
+    	            if (log.isDebugEnabled()) {
+    	                log.debug("Authenticator is returning false from call to public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)");
+    	            }
+    	            return false;
+    	        }
+            } else {
+                // this could be a warning rather than debug, but in certain environments it might happen more often.
+                if (log.isDebugEnabled()) {
+                    log.debug("Got null user after creating user " + username + " so could not update it to set its fullname or email.");
+                }
+            }
+        } else {
+            if (config.isUpdateInfo()) {
+                updateUser(user, fullName, emailAddress);
+            }
+        }
+
+        if (config.isUpdateRoles() || newUser) {
+            updateGroupMemberships(request, user, crowdService, crowdUser);
+        }
+
+        // kick off login related methods
+        loginSuccessful(request, response, userid, user, remoteHost, remoteIP);
+
+        if (log.isDebugEnabled()) {
+            log.debug("Authenticator is returning true from call to public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)");
+        }
+
+        return true;
+    }
+    
+	private void loginSuccessful(HttpServletRequest request,
+			HttpServletResponse response, String username, Principal user,
+			String remoteHost, String remoteIP) {
+		if (log.isDebugEnabled()) {
+			log.debug("Logging in user "
+					+ ((user != null) ? user.getName() : username)
+					+ ". request=" + request + ", response=" + response
+					+ ", username=" + username + ", user=" + user
+					+ ((user != null) ? ", user.getName=" + user.getName():"" ) + ", remoteHost="
+					+ remoteHost + ", remoteIP=" + remoteIP);
+		}
+
+		if (user != null) {
+			// SHBL-50 - code provided by Joseph Clark and Erkki Aalto to do
+			// postlogin updates.
+			// Some of this will break eventually with new Confluence/Crowd
+			// versions.
+			putPrincipalInSessionContext(request, user);
+		}
+		// TODO: Joe Clark uses getElevatedSecurityGuard() vs.
+		// getLoginManager(). Which should we use?
+		// see:
+		// https://bitbucket.org/jaysee00/example-confluence-sso-authenticator/src/381eb95ebc08/src/main/java/com/atlassian/confluence/seraph/example/ExampleSSOAuthenticator.java
+		getLoginManager().onLoginAttempt(request, username, true);
+		//getEventPublisher().publish(
+		//new LoginEvent(this, username, request.getSession().getId(),
+		//				remoteHost, remoteIP));
+		//LoginReason.OK.stampRequestResponse(request, response);
+	}
+    
+    private void loginFailed(HttpServletRequest request, String username, String remoteHost, String remoteIP, String reason) {
+        if (log.isDebugEnabled()) {
+            log.debug("Login failed for user " + username + ". request=" + request + ", username=" + username + ", remoteHost=" + remoteHost + ", remoteIP="+ remoteIP + ", reason=" + reason);
+        }
+        if (username != null) {
+            getLoginManager().onLoginAttempt(request, username, false);
+        }
+        //getEventPublisher().publish(new LoginFailedEvent(this, reason, request.getSession().getId(),
+        //        remoteHost, remoteIP));
+    }
+
+    private void updateGroupMemberships(HttpServletRequest request, Principal user, CrowdService crowdService, User crowdUser) {
+    	if (user == null) {
+            if (log.isDebugEnabled()) {
+                log.debug("User is null, so can't update group memberships.");
+            }
+        } else {
+	    	Set roles = new HashSet();
+	
+	        // Add user to groups.
+	        getRolesFromHeader(request, roles);
+	        assignUserToRoles(user, config.getDefaultRoles(), crowdService, crowdUser);
+	        assignUserToRoles(user, roles, crowdService, crowdUser);
+	
+	        // Make sure we don't purge default roles either
+	        roles.addAll(config.getDefaultRoles());
+	        purgeUserRoles(user, roles);
+        }
+    }
+
+    public Principal getUser(HttpServletRequest request, HttpServletResponse response) {
+        
+        String remoteIP = request.getRemoteAddr();
+        String remoteHost = request.getRemoteHost();
+
+        if (log.isDebugEnabled()) {
+            log.debug("getUser(...) called. requestURL=" + request.getRequestURL() + ", remoteIP=" + remoteIP + ", remoteHost=" + remoteHost);
+        }
+
+        // Check if the user is already logged in
+        Principal user = getUserFromSession(request);
+        if (user != null) {
+            if (log.isDebugEnabled()) {
+                log.debug("" + user.getName() + " already logged in, returning.");
+                log.debug("Authenticator is returning " + user + " from call to public Principal getUser(HttpServletRequest request, HttpServletResponse response)");
+            }
+            return user;
+        }
+
+	// Since they aren't logged in, get the user name from
+        // the REMOTE_USER header
+        String userid = createSafeUserid(getLoggedInUser(request));
+
+        if ((userid == null) || (userid.length() <= 0)) {
+            if (log.isDebugEnabled()) {
+                log.debug("Remote user was null or empty, can not perform authentication.");
+            }
+
+            loginFailed(request, userid, remoteHost, remoteIP, "NoUsername");
+
+            if (log.isDebugEnabled()) {
+                log.debug("Authenticator is returning null from call to public Principal getUser(HttpServletRequest request, HttpServletResponse response)");
+            }
+            return null;
+        }
+
+        // Now that we know we will be trying to log the user in,
+        // let's see if we should reload the config file first
+        checkReloadConfig();
+
+        // Convert username to all lowercase
+        if (config.isUsernameConvertCase()) {
+            userid = convertUsername(userid);
+        }
+
+	CrowdService crowdService = getCrowdService();
+        if (crowdService == null) {
+            loginFailed(request, userid, remoteHost, remoteIP, "AuthenticatorConfigFailure");
+            if (log.isDebugEnabled()) {
+                log.debug("Authenticator is throwing RuntimeException from call to public boolean login(HttpServletRequest request, HttpServletResponse response, String username, String password, boolean cookie)");
+            }
+
+            throw new RuntimeException("crowdService was not wired in RemoteUserAuthenticator");
+        }
+
+	// ensure user is active
+        User crowdUser = crowdService.getUser(userid);
+        if (crowdUser != null && !crowdUser.isActive()) {
+            log.info("Login failed for user '" + userid + "', because user is set as inactive. remoteIP=" + remoteIP + " remoteHost=" + remoteHost);
+
+            loginFailed(request, userid, remoteHost, remoteIP, "UserInactive");
+
+            if (log.isDebugEnabled()) {
+                log.debug("Authenticator is returning null from call to public Principal getUser(HttpServletRequest request, HttpServletResponse response)");
+            }
+            return null;
+        }
+
+        // Pull name and address from headers
+        String fullName = getFullName(request, userid);
+        String emailAddress = getEmailAddress(request);
+
+        // Try to get the user's account based on the user name
+        user = getUser(userid);
+
+        boolean newUser = false;
+
+        // User didn't exist or was problem getting it. we'll try to create it
+        // if we can, otherwise will try to get it again.
+        if (user == null) {
+            if (config.isCreateUsers()) {
+                createUser(userid, fullName, emailAddress);
+                newUser = true;
+            } else {
+                if (log.isDebugEnabled()) {
+                    log.debug("Configuration does NOT allow creation of new user accounts, authentication will fail for " +
+                            userid + ". Login attempt by '" + userid + "' failed.");
+                }
+
+                loginFailed(request, userid, remoteHost, remoteIP, "CreateUserDisabled");
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Authenticator is returning null from call to public Principal getUser(HttpServletRequest request, HttpServletResponse response)");
+                }
+                return null;
+            }
+
+            user = getUser(userid);
+
+            if (user != null) {
+                // ensure user is active
+                crowdUser = crowdService.getUser(userid);
+                if (crowdUser != null && !crowdUser.isActive()) {
+                    log.info("Login failed for user '" + userid + "', because user is set as inactive. remoteIP=" + remoteIP + " remoteHost=" + remoteHost);
+
+                    loginFailed(request, userid, remoteHost, remoteIP, "UserInactive");
+
+                    if (log.isDebugEnabled()) {
+                        log.debug("Authenticator is returning null from call to public Principal getUser(HttpServletRequest request, HttpServletResponse response)");
+                    }
+                    return null;
+                }
+            } else {
+                // If user is still null, probably we're using an
+                // external user database like LDAP. Either REMOTE_USER
+                // isn't present there or is being filtered out, e.g.
+                // by userSearchFilter
+                if (log.isDebugEnabled()) {
+                    log.debug("User does not exist and cannot create it. Login attempt by '" + userid + "' failed.");
+                }
+
+                loginFailed(request, userid, remoteHost, remoteIP, "CannotCreateUser");
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Authenticator is returning null from call to public Principal getUser(HttpServletRequest request, HttpServletResponse response)");
+                }
+                return null;
+            }
+        } else {
+            if (config.isUpdateInfo()) {
+                updateUser(user, fullName, emailAddress);
+            }
+        }
+
+        if (config.isUpdateRoles() || newUser) {
+            updateGroupMemberships(request, user, crowdService, crowdUser);
+        }
+
+        loginSuccessful(request, response, user.getName(), user, remoteHost, remoteIP);
+
+        if (log.isDebugEnabled()) {
+            log.debug("Authenticator is returning " + user + " from call to public Principal getUser(HttpServletRequest request, HttpServletResponse response)");
+        }
+        
+        return user;
+    }
+
+
+    private String createSafeUserid(String originalRemoteuser) {
+        // Possible to have multiple mappers defined, but only 1 will produce the desired outcome.
+        Set possibleRemoteUsers = new HashSet();
+        Collection mappers = config.getRemoteUserMappings();
+
+        for (Iterator mapperIt = mappers.iterator(); mapperIt.hasNext(); ) {
+            GroupMapper mapper = (GroupMapper) mapperIt.next();
+
+            String[] results = (String[]) StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(
+                    mapper.process(originalRemoteuser)).toArray(new String[0]);
+
+            if (results.length != 0) {
+                possibleRemoteUsers.addAll(Arrays.asList(results));
+            }
+        }
+
+        if (possibleRemoteUsers.isEmpty()) {
+            if (log.isDebugEnabled()) {
+                log.debug("Remote user is returned as is, mappers do not matched.");
+            }
+
+            return originalRemoteuser;
+        }
+
+        if (log.isDebugEnabled() && possibleRemoteUsers.size() > 1) {
+            log.debug("Remote user has been transformed, but there are too many results, choosing one that seems suitable");
+        }
+
+        // Try the next one.
+        // TODO: Is this adequate?
+        String output = possibleRemoteUsers.iterator().next().toString();
+        return remoteUserCharsReplacement(output);
+    }
+
+    private String remoteUserCharsReplacement(String remoteUser) {
+        // If remoteuser.replace is specified, process it. It has the format of pair-wise value, occurences of 1st entry
+        // regex is replaced with what specified on the second entry. The list is comma or semi-colon separated (which
+        // means it is pretty obvious a comma or semi-colon can't be used in the content replacement.
+        Iterator it = config.getRemoteUserReplacementChars();
+
+        while (it.hasNext()) {
+            String replaceFromRegex = it.next().toString();
+
+            // Someone didn't fill up pair-wise entry, ignore this regex.
+            if (!it.hasNext()) {
+                if (replaceFromRegex.length() != 0) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Character replacements specified for Remote User regex is incomplete, make sure the entries are pair-wise, skipping...");
+                    }
+                }
+                break;
+            }
+
+            String replacement = it.next().toString();
+
+            // We are not going to replace empty string, so skip it.
+            if (replaceFromRegex.length() == 0) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Empty string is found in Remote User replaceFrom regex, skipping...");
+                }
+
+                continue;
+            }
+
+            try {
+                remoteUser = remoteUser.replaceAll(replaceFromRegex, replacement);
+            } catch (Throwable t) {
+                log.warn("Failed to replace certain character entries in \"Remote User\" matching regex=\"" + replaceFromRegex + "\", ignoring...");
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Failed to replace certain character entries in Remote User", t);
+                }
+            }
+        }
+        return remoteUser;
+    }
+
+    private String createFullNameUsingMapping(String originalFullNameHeaderValue, List values) {
+        // It is possible to have multiple mappers defined, but only one will produce the desired outcome.
+        Set possibleFullNames = new HashSet();
+        Collection mappers = config.getFullNameMappings();
+
+        for (Iterator mapperIt = mappers.iterator(); mapperIt.hasNext(); ) {
+            GroupMapper mapper = (GroupMapper) mapperIt.next();
+            String[] results = (String[]) StringUtil.
+                    toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(
+                            mapper.process(originalFullNameHeaderValue)).toArray(new String[0]);
+
+            if (results.length != 0) {
+                possibleFullNames.addAll(Arrays.asList(results));
+            }
+        }
+
+        if (possibleFullNames.isEmpty()) {
+            if (log.isDebugEnabled()) {
+                log.debug("Full Name header value returned. Mappers do not match, so will use first value in list.");
+            }
+
+            return (String) values.get(0);
+        }
+
+        if (log.isDebugEnabled() && possibleFullNames.size() > 1) {
+            log.debug("Full name has been transformed, but more than one result, so choosing one that seems suitable.");
+        }
+
+        //just get a random one
+        String output = possibleFullNames.iterator().next().toString();
+        return fullNameCharsReplacement(output);
+    }
+
+    private String fullNameCharsReplacement(String fullName) {
+        // If fullname.replace is specified, process it. It has the format of pair-wise value, occurences of 1st entry
+        // regex is replaced with what specified on the second entry. The list is comma or semi-colon separated (which
+        // means it is pretty obvious a comma or semi-colon can't be used in the content replacement.
+        Iterator it = config.getFullNameReplacementChars();
+
+        while (it.hasNext()) {
+            String replaceFromRegex = it.next().toString();
+
+            // Someone didn't fill up pair-wise entry, ignore this regex.
+            if (!it.hasNext()) {
+                if (replaceFromRegex.length() != 0) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Character replacements specified for Full Name regex is incomplete, make sure the entries are pair-wise, skipping...");
+                    }
+                }
+
+                break;
+            }
+
+            String replacement = it.next().toString();
+
+            // We are not going to replace empty string, so skip it.
+            if (replaceFromRegex.length() == 0) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Empty string is found in Full Name replaceFrom regex, skipping...");
+                }
+
+                continue;
+            }
+
+            try {
+                fullName = fullName.replaceAll(replaceFromRegex, replacement);
+            } catch (Exception e) {
+                log.warn("Fail to replace certain character entries in username matching regex=\"" + replaceFromRegex +
+                        "\".");
+                if (log.isDebugEnabled()) {
+                    log.debug("Failed to replace certain character entries in Remote User", e);
+                }
+            }
+        }
+
+        return fullName;
+    }
+
+    // avoid "Write operations are not allowed in read-only mode" per Joseph Clark of Atlassian in
+    // https://answers.atlassian.com/questions/25160/crowdservice-updateuser-causes-write-operations-are-not-allowed-in-read-only-mode
+    // https://developer.atlassian.com/display/CONFDEV/Hibernate+Sessions+and+Transaction+Management+Guidelines
+    private void addGroup(final CrowdService crowdService, final Group group) {
+        if (group != null) {
+            try {
+                crowdService.addGroup(group);
+            } catch (Throwable t) {
+                log.error("Failed to add group '" + group.getName() + "'!", t);
+            }
+        } else {
+            log.warn("Cannot add null group!");
+        }
+    }
+
+    // avoid "Write operations are not allowed in read-only mode" per Joseph Clark of Atlassian in
+    // https://answers.atlassian.com/questions/25160/crowdservice-updateuser-causes-write-operations-are-not-allowed-in-read-only-mode
+    // https://developer.atlassian.com/display/CONFDEV/Hibernate+Sessions+and+Transaction+Management+Guidelines
+    private void addUserToGroup(final CrowdService crowdService, final User crowdUser, final Group group) {
+        if (crowdUser == null) {
+            log.warn("Cannot add null user to group!");
+        } else if (group == null) {
+            log.warn("Cannot add user to null group!");
+        } else {
+            try {
+                crowdService.addUserToGroup(crowdUser, group);
+            } catch (Throwable t) {
+                log.error("Failed to add user " + crowdUser.getName() + " to group '" + group.getName() + "'!", t);
+            }
+        }
+    }
+
+    // avoid "Write operations are not allowed in read-only mode" per Joseph Clark of Atlassian in
+    // https://answers.atlassian.com/questions/25160/crowdservice-updateuser-causes-write-operations-are-not-allowed-in-read-only-mode
+    // https://developer.atlassian.com/display/CONFDEV/Hibernate+Sessions+and+Transaction+Management+Guidelines
+    private void removeUserFromGroup(final CrowdService crowdService, final User crowdUser, final Group group) {
+        if (crowdUser == null) {
+            log.warn("Cannot remove null user from group!");
+        } else if (group == null) {
+            log.warn("Cannot remove user from null group!");
+        } else {
+            try {
+                crowdService.removeUserFromGroup(crowdUser, group);
+            } catch (Throwable t) {
+                log.error("Failed to remove user " + crowdUser.getName() + " from group '" + group.getName() + "'!", t);
+            }
+        }
+    }
+
+    // avoid "Write operations are not allowed in read-only mode" per Joseph Clark of Atlassian in
+    // https://answers.atlassian.com/questions/25160/crowdservice-updateuser-causes-write-operations-are-not-allowed-in-read-only-mode
+    // https://developer.atlassian.com/display/CONFDEV/Hibernate+Sessions+and+Transaction+Management+Guidelines
+    private void createUser(final CrowdService crowdService, final String username, final String fullName, final String email) {
+        if (username != null) {
+            try {
+                byte[] randomPass = new byte[16];
+                prng.nextBytes(randomPass);
+                ImmutableUser.Builder userBuilder = new ImmutableUser.Builder();
+                userBuilder.active(true);
+                userBuilder.directoryId(0);
+            	userBuilder.displayName(fullName);
+            	userBuilder.emailAddress(email);
+            	userBuilder.name(username);
+                crowdService.addUser(userBuilder.toUser(), Base64.encodeBase64String(randomPass));
+            } catch (Throwable t) {
+                log.error("Failed to create user '" + username + "'!", t);
+            }
+        } else {
+            log.warn("Cannot add user with null username!");
+        }
+    }
+
+    // avoid "Write operations are not allowed in read-only mode" per Joseph Clark of Atlassian in
+    // https://answers.atlassian.com/questions/25160/crowdservice-updateuser-causes-write-operations-are-not-allowed-in-read-only-mode
+    // https://developer.atlassian.com/display/CONFDEV/Hibernate+Sessions+and+Transaction+Management+Guidelines
+    private void updateUser(final CrowdService crowdService, final User crowdUser) {
+        if (crowdUser != null) {
+            try {
+                crowdService.updateUser(crowdUser);
+            } catch (Throwable t) {
+               log.error("Failed to update user '" + crowdUser.getName() + "'!", t);
+            }
+        } else {
+            log.warn("Cannot update null user!");
+        }
+    }
+
+    public CrowdService getCrowdService() {
+        return ComponentAccessor.getCrowdService();
+    }
+
+    public LoginManager getLoginManager() {
+        return (LoginManager) ComponentAccessor.getComponent(LoginManager.class);
+    }
+
+    public UserManager getUserManager() {
+        return ComponentAccessor.getUserManager();
+    }
+
+    public GroupManager getGroupManager() {
+        return ComponentAccessor.getGroupManager();
+    }
+}
diff --git a/src/main/java/net/shibboleth/tools/jira/authn/ShibAuthConfigLoader.java b/src/main/java/net/shibboleth/tools/jira/authn/ShibAuthConfigLoader.java
new file mode 100644
index 0000000..c91a682
--- /dev/null
+++ b/src/main/java/net/shibboleth/tools/jira/authn/ShibAuthConfigLoader.java
@@ -0,0 +1,451 @@
+/*
+ Copyright (c) 2008-2012, Shibboleth Authenticator for Confluence Team
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ * Neither the name of the Shibboleth Authenticator for Confluence Team
+   nor the names of its contributors may be used to endorse or promote
+   products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+*/
+
+package net.shibboleth.tools.jira.authn;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.*;
+
+public class ShibAuthConfigLoader {
+
+    private final static Log log =
+            LogFactory.getLog(ShibAuthConfigLoader.class);
+
+    public static ShibAuthConfiguration getShibAuthConfiguration(ShibAuthConfiguration oldConfig) {
+        if (log.isDebugEnabled()) {
+            if (oldConfig == null) {
+                log.debug("Initializing authenticator using property resource " + ShibAuthConstants.PROPERTIES_FILE);
+            } else {
+                log.debug("Reloading configuration from authenticator from file " + oldConfig.getConfigFile());
+            }
+        }
+
+        ShibAuthConfiguration config = new ShibAuthConfiguration();
+
+        try {
+            InputStream propsIn = null;
+            if (oldConfig == null) {
+                propsIn = RemoteUserAuthenticator.class.getResourceAsStream(ShibAuthConstants.PROPERTIES_FILE);
+                // getResourceAsStream silently returns null if there's a problem.
+                if (propsIn == null) {
+                    throw new IOException();
+                }
+            } else {
+                propsIn = new FileInputStream(oldConfig.getConfigFile());
+            }
+
+            Properties configProps = new Properties();
+            configProps.load(propsIn);
+
+            // Load create.users property.
+            config.setCreateUsers(
+                    Boolean.valueOf(configProps.getProperty(ShibAuthConstants.CREATE_USERS, "true")).booleanValue());
+
+            if (log.isDebugEnabled()) {
+                log.debug("Setting create new users to " + config.isCreateUsers());
+            }
+
+            // Load update.info property.
+            config.setUpdateInfo(Boolean.valueOf(configProps.getProperty(ShibAuthConstants.UPDATE_INFO, "true")).
+                    booleanValue());
+
+            if (log.isDebugEnabled()) {
+                log.debug("Setting update user information to " + config.isUpdateInfo());
+            }
+
+            // Load update.lastLoginDate property.
+            config.setUpdateLastLogin(
+                    Boolean.valueOf(configProps.getProperty(ShibAuthConstants.UPDATE_LAST_LOGIN_DATE, "true")).booleanValue()
+            );
+
+            if (log.isDebugEnabled()) {
+                log.debug("Setting update user's last login date and previous login date information to " +
+                        config.isUpdateLastLogin());
+            }
+
+            // Load update.roles property.
+            config.setUpdateRoles(
+                    Boolean.valueOf(configProps.getProperty(ShibAuthConstants.UPDATE_ROLES, "true")).booleanValue());
+
+            if (log.isDebugEnabled()) {
+                log.debug("Setting update user roles to " + config.isUpdateRoles());
+            }
+
+            // Load reload.config property.
+            config.setReloadConfig(
+                    Boolean.valueOf(configProps.getProperty(ShibAuthConstants.RELOAD_CONFIG, "false")).booleanValue());
+
+            if (log.isDebugEnabled()) {
+                log.debug("Setting reload config to " + config.isReloadConfig());
+            }
+
+            // Load reload.config.check.interval property.
+            String reloadConfigCheckIntervalS = configProps.getProperty(ShibAuthConstants.RELOAD_CONFIG_CHECK_INTERVAL, "5000");
+            if (reloadConfigCheckIntervalS != null) {
+                config.setReloadConfigCheckInterval(Long.valueOf(reloadConfigCheckIntervalS).longValue());
+
+                if (log.isDebugEnabled()) {
+                    log.debug("Setting reload config check interval to " + config.getReloadConfigCheckInterval());
+                }
+            }
+
+            // Load convert.to.utf8 property.
+            config.setConvertToUTF8(
+                    Boolean.valueOf(configProps.getProperty(ShibAuthConstants.CONVERT_TO_UTF8, "false")).booleanValue());
+
+            if (log.isDebugEnabled()) {
+                log.debug("Setting convert header values to UTF-8 to " + config.isConvertToUTF8());
+            }
+
+            // Load dynamicheaders.output.tolowercase property. Default is true.
+            config.setOutputToLowerCase(Boolean.valueOf(
+                    configProps.getProperty(ShibAuthConstants.ROLES_OUTPUT_TOLOWER, "true")).booleanValue());
+
+            if (log.isDebugEnabled()) {
+                log.debug("Setting convert group output values to lowercase = " + config.isOutputToLowerCase());
+            }
+
+            // Load default roles.
+            List defaultRoles = new ArrayList();
+
+            String roles = configProps.getProperty(ShibAuthConstants.DEFAULT_ROLES);
+
+            if (roles != null) {
+                defaultRoles.addAll(StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(roles));
+
+                if (log.isDebugEnabled()) {
+                    for (Iterator it = defaultRoles.iterator(); it.hasNext(); ) {
+                        log.debug("Adding role " + it.next().toString() + " to list of default user roles");
+                    }
+                }
+            }
+
+            config.setDefaultRoles(defaultRoles);
+            config.setRemoteUserHeaderName(configProps.getProperty(ShibAuthConstants.REMOTE_USER_HEADER_NAME_PROPERTY, "REMOTE_USER"));
+
+            if (log.isDebugEnabled()) {
+                log.debug("HTTP Header that may contain user's username set to: " + config.getRemoteUserHeaderName());
+            }
+
+            config.setFullNameHeaderName(configProps.getProperty(ShibAuthConstants.FULLNAME_HEADER_NAME_PROPERTY, "CONF_FULL_NAME"));
+
+            if (log.isDebugEnabled()) {
+                log.debug("HTTP Header that may contain user's full name set to: " + config.getFullNameHeaderName());
+            }
+
+            config.setEmailHeaderName(configProps.getProperty(ShibAuthConstants.EMAIL_HEADER_NAME_PROPERTY, "CONF_EMAIL"));
+
+            if (log.isDebugEnabled()) {
+                log.debug("HTTP Header that may contain user's email address set to: " + config.getEmailHeaderName());
+            }
+
+            config.setUsernameConvertCase(Boolean.valueOf(
+                    configProps.getProperty(ShibAuthConstants.USERNAME_CASE_CONVERT_PROPERTY, "true")).
+                    booleanValue());
+
+            if (log.isDebugEnabled()) {
+                log.debug("Setting usernameConvertCase to " + config.isUsernameConvertCase());
+            }
+
+            // warn about property no longer supported
+            if (Boolean.valueOf(configProps.getProperty(ShibAuthConstants.USING_SHIB_LOGIN_FILTER, "false")).booleanValue()) {
+                log.warn("Note: using.shib.login.filter=true is no longer supported by Confluence Shibboleth Authenticator. Please remove using.shib.login.filter from remoteUserAuthenticator.properties.");
+            }
+
+            loadGroupMapping(config, configProps);
+            loadPurgeGroupMapping(config, configProps);
+            loadRemoteUserMapping(config, configProps);
+            loadFullNameMapping(config, configProps);
+
+            // Set the name of the config file for automatic reloading
+            if (config.isReloadConfig()) {
+
+                URL configURL = RemoteUserAuthenticator.class.getResource(ShibAuthConstants.PROPERTIES_FILE);
+
+                if ((configURL == null) || !configURL.getProtocol().equals("file")) {
+                    log.warn("Configuration file is not a file URL, cannot setup automatic reloading from: " +
+                            configURL);
+                } else {
+                    String configFile = configURL.getFile();
+                    long configFileLastModified = new File(configFile).lastModified();
+
+                    config.setConfigFile(configFile);
+                    config.setConfigFileLastModified(configFileLastModified);
+                    config.setConfigFileLastChecked(System.currentTimeMillis());
+
+                    log.info("Setting config file name to " + configFile + " with a lastModified stamp of " +
+                            configFileLastModified + " and a last checked stamp of " +
+                            config.getConfigFileLastChecked());
+                }
+            }
+        } catch (IOException e) {
+            log.warn("Unable to read properties file, using default properties", e);
+        }
+
+        return config;
+    }
+
+    private static void loadPurgeGroupMapping(ShibAuthConfiguration config,
+                                              Properties configProps) {
+        List purgeRolesRegex = StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(
+                configProps.getProperty(ShibAuthConstants.PURGE_ROLES));
+        if (purgeRolesRegex.isEmpty()) {
+            if (log.isDebugEnabled()) {
+                log.debug("No roles regex specified, no roles will be purged.");
+            }
+
+            return;
+        }
+
+        // Cheat a bit, the syntax:
+        // purge.roles = regex1, regex2
+        // We convert it to GroupMapper's syntax where
+        // match = regex1
+        // transform = $0 (the whole input string; equiv to null here)
+        // Case insensitive.
+        Collection purgeRolesGroups = new ArrayList(purgeRolesRegex.size());
+        for (int i = 0; i < purgeRolesRegex.size(); i++) {
+            String regex = purgeRolesRegex.get(i).toString();
+
+            purgeRolesGroups.add(new GroupMapper("purge-" + i, regex, null, false));
+
+            if (log.isDebugEnabled()) {
+                log.debug("Roles matching (" + regex + ") are to be purged.");
+            }
+        }
+
+        config.setPurgeMappings(purgeRolesGroups);
+    }
+
+    private static void loadRemoteUserMapping(ShibAuthConfiguration config,
+                                              Properties configProps) {
+        // remoteuser=remoteusermap
+        // remoteuser.replace=\\#,A,\\%,c,-,,
+        // remoteuser.remoteusermap.match=some-regex
+        List remoteuserlabels = StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(
+                configProps.getProperty(ShibAuthConstants.REMOTEUSER_PREFIX));
+
+        if (remoteuserlabels.isEmpty()) {
+            //clear the headers, future processing wil bypass when empty
+            config.setRemoteUserMappings(Collections.EMPTY_LIST);
+            return;
+        }
+
+        List replacements = StringUtil.toListOfStringsDelimitedByCommaOrSemicolon(
+                configProps.getProperty(ShibAuthConstants.REMOTEUSER_REPLACEMENT)
+        );
+
+        config.setRemoteUserReplacementChars(replacements);
+
+        List mappers = new ArrayList();
+
+        mappers.addAll(loadMappers(ShibAuthConstants.REMOTEUSER_MAP_PREFIX, configProps, remoteuserlabels));
+
+        if (mappers.isEmpty()) {
+            if (log.isDebugEnabled()) {
+                log.debug("No RemoteUser mapper handler defined in \"" + ShibAuthConstants.REMOTEUSER_PREFIX +
+                        "\", remoteuser will be left untouched.");
+            }
+
+            config.setRemoteUserMappings(Collections.EMPTY_LIST);
+
+            return;
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug(
+                    "RemoteUser mapping is defined in config, transformation of remote user will happen during login.");
+        }
+
+        config.setRemoteUserMappings(mappers);
+    }
+
+    private static void loadFullNameMapping(ShibAuthConfiguration config, Properties configProps) {
+        // fullname=fullnamemap
+        // fullname.replace=\\#,A,\\%,c,-,,
+        // fullname.fullnamemap.match=some-regex
+        List fullnamelabels = StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(
+                configProps.getProperty(ShibAuthConstants.FULL_NAME_PREFIX));
+
+        if (fullnamelabels.isEmpty()) {
+            //clear the headers, future processing wil bypass when empty
+            config.setFullNameMappings(Collections.EMPTY_LIST);
+            return;
+        }
+
+        List replacements = StringUtil.toListOfStringsDelimitedByCommaOrSemicolon(
+                configProps.getProperty(ShibAuthConstants.FULL_NAME_REPLACEMENT)
+        );
+
+        config.setFullNameReplacementChars(replacements);
+
+        List mappers = new ArrayList();
+
+        mappers.addAll(loadMappers(ShibAuthConstants.FULL_NAME_MAP_PREFIX, configProps, fullnamelabels));
+
+        if (mappers.isEmpty()) {
+            if (log.isDebugEnabled()) {
+                log.debug("No FullName mapper handler defined in \"" + ShibAuthConstants.FULL_NAME_PREFIX +
+                        "\", full name will be left untouched.");
+            }
+
+            config.setFullNameMappings(Collections.EMPTY_LIST);
+
+            return;
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("FullName mapping is defined in config, transformation of full name will happen during login.");
+        }
+
+        config.setFullNameMappings(mappers);
+    }
+
+    private static void loadGroupMapping(ShibAuthConfiguration config,
+                                         Properties configProps) {
+
+        config.setAutoCreateGroup(Boolean.valueOf(
+                configProps.getProperty(ShibAuthConstants.AUTO_CREATE_GROUP, "false")).
+                booleanValue());
+
+        if (log.isDebugEnabled()) {
+            log.debug("Setting automatic creation of new group to " + config.isAutoCreateGroup());
+        }
+
+        // Load dynamic roles property
+        // #header.dynamicroles.SHIB-EP-ENTITLEMENT=mapper1, mapper2
+        // "headers" contains the list of entries such as "SHIB-EP-ENTITLEMENT"
+        List headers = config.listPostfixes((String[]) configProps.keySet().toArray(new String[0]),
+                ShibAuthConstants.ROLES_HEADER_PREFIX);
+
+        if (headers.isEmpty()) {
+            // No header is specified for dynamicgroup.
+            log.info(
+                    "No attribute header defined for dynamicroles, deactivating it.");
+            // Clear the headers, future processing wil bypass when empty.
+            config.setGroupMappings(Collections.EMPTY_MAP);
+            return;
+        }
+
+        // Map<mapper_string_name, GroupMapper>
+        Map allMappers = new HashMap();
+
+        // Map<header_name, Collection<GroupMapper>>
+        Map groupMappings = new HashMap();
+        for (Iterator headerIt = headers.iterator(); headerIt.hasNext(); ) {
+            String header = headerIt.next().toString();
+            List definedMapperStrings = StringUtil.toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(
+                    configProps.getProperty(ShibAuthConstants.ROLES_HEADER_PREFIX + header));
+            List mappers = new ArrayList();
+
+            // Check if the definedMapperStrings already loaded previously. If so, just reuse the same definition.
+            for (Iterator definedIterator = definedMapperStrings.iterator(); definedIterator.hasNext(); ) {
+                GroupMapper gMapper = (GroupMapper) allMappers.get(definedIterator.next().toString());
+                if (gMapper != null) {
+                    mappers.add(gMapper);
+
+                    // Remove it from definedMapperStrings so that we don't load it twice.
+                    definedIterator.remove();
+                }
+            }
+
+            mappers.addAll(loadMappers(ShibAuthConstants.ROLES_ATTRIB_PREFIX, configProps, definedMapperStrings));
+
+            if (mappers.isEmpty()) {
+                if (log.isDebugEnabled()) {
+                    log.debug("No group mapper handler defined in \"" + ShibAuthConstants.ROLES_HEADER_PREFIX + header +
+                            "\", ignoring this header.");
+                }
+
+                continue;
+            }
+
+            // Cache the mappers and print them.
+            StringBuffer sb = new StringBuffer();
+            for (Iterator it = mappers.iterator(); it.hasNext(); ) {
+                GroupMapper mapper = (GroupMapper) it.next();
+                String label = mapper.toString();
+                sb.append(label);
+                if (it.hasNext()) {
+                    sb.append(", ");
+                }
+
+                // Fill up allMappers, so that we dont need to reload existing mappers later.
+                if (!allMappers.containsKey(label)) {
+                    allMappers.put(label, mapper);
+                }
+            }
+
+            if (log.isDebugEnabled()) {
+                log.debug("Successfully loading mapper for header=" + header + ", handlers=" + sb.toString());
+            }
+
+            groupMappings.put(header, mappers);
+        }
+
+        config.setGroupMappings(groupMappings);
+    }
+
+    private static Collection loadMappers(String mapperPrefix, Properties configProps, List mapperStrings) {
+        if (mapperStrings == null || mapperStrings.isEmpty()) {
+            return Collections.EMPTY_LIST;
+        }
+
+        Collection mappers = new ArrayList();
+
+        for (int i = 0; i < mapperStrings.size(); i++) {
+            String name = (String) mapperStrings.get(i);
+            String mapperStr = mapperPrefix + name;
+            String match = configProps.getProperty(
+                    mapperStr + ShibAuthConstants.PART_MATCH);
+            String transform = configProps.getProperty(
+                    mapperStr + ShibAuthConstants.PART_TRANSFORM);
+
+            if (match == null && transform == null) {
+                log.warn(
+                        "Fail to load group mapper with label=" + name + ", ignoring this mapper.");
+                continue;
+            }
+
+            boolean sensitive = Boolean.valueOf(configProps.getProperty(mapperStr + ShibAuthConstants.PART_SENSITIVE,
+                    "true")).booleanValue();
+            GroupMapper mapper = new GroupMapper(name, match, transform, sensitive);
+            mappers.add(mapper);
+        }
+
+        return mappers;
+    }
+}
diff --git a/src/main/java/net/shibboleth/tools/jira/authn/ShibAuthConfiguration.java b/src/main/java/net/shibboleth/tools/jira/authn/ShibAuthConfiguration.java
new file mode 100644
index 0000000..10378fa
--- /dev/null
+++ b/src/main/java/net/shibboleth/tools/jira/authn/ShibAuthConfiguration.java
@@ -0,0 +1,426 @@
+/*
+ Copyright (c) 2008-2012, Shibboleth Authenticator for Confluence Team
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ * Neither the name of the Shibboleth Authenticator for Confluence Team
+   nor the names of its contributors may be used to endorse or promote
+   products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package net.shibboleth.tools.jira.authn;
+
+import java.util.*;
+
+public class ShibAuthConfiguration {
+
+    /**
+     * Collection of mappers capable of transforming remote-user into
+     * something meaningful for confluence.
+     */
+    private Collection remoteUserMappings = new ArrayList();
+
+    /**
+     * Contains lists of character replacements that need to be
+     * applied to remote user. The list is processed pair-wise.
+     * Null (empty) is permitted in the list, which means
+     * the matched character will be removed from remote user
+     */
+    private List remoteUserReplacementChars = new ArrayList();
+
+    /**
+     * Collection of mappers capable of transforming full name into
+     * something meaningful for confluence.
+     */
+    private Collection fullNameMappings = new ArrayList();
+
+    /**
+     * Contains lists of character replacements that need to be
+     * applied to full name. The list is processed pair-wise.
+     * Null (empty) is permitted in the list, which means
+     * the matched character will be removed from full name
+     */
+    private List fullNameReplacementChars = new ArrayList();
+
+    /**
+     * Set of header names to be watchful for dynamic roles. Content has
+     * format of Map<attribHeader, Collection<GroupMapper>>
+     * where attribHeader is a string, e.g. SHIB-EP-ENTITLEMENT
+     */
+    private Map groupMappings = new HashMap();
+
+    /**
+     * list of all mappers that should be doing the purging
+     */
+    private List purgeMappings = new ArrayList();
+
+    /**
+     * Whether to create accounts for new users or not
+     */
+    private boolean createUsers;
+
+    /**
+     * Default roles for newly created users
+     */
+    private List defaultRoles;
+
+    /**
+     * Automatically reload the configuration file when changed
+     */
+    private boolean reloadConfig;
+
+    /**
+     * When reloading the configuration file, how long to wait (in milliseconds) between
+     * checking the configuration file for changes?
+     */
+    private long reloadConfigCheckInterval;
+
+    /**
+     * Name of the configuration file to be reloaded
+     */
+    private String configFile;
+
+    /**
+     * Last modified stamp of the configuration file
+     */
+    private long configFileLastModified;
+
+    /**
+     * System time at when the configuration file was checked the last time
+     */
+    private long configFileLastChecked;
+
+    /**
+     * HTTP Header name that contains a user's username
+     */
+    private String remoteUserHeaderName;
+
+    /**
+     * HTTP Header name that contains a user's email address
+     */
+    private String emailHeaderName;
+
+    /**
+     * HTTP Header name that contains a user's full name
+     */
+    private String fullNameHeaderName;
+
+    /**
+     * Whether or not to update name/email info for previously created users
+     */
+    private boolean updateInfo;
+
+    /**
+     * Whether to update roles for new users or not
+     */
+    private boolean updateRoles;
+
+    /**
+     * Whether to convert fields to UTF8
+     */
+    private boolean convertToUTF8;
+
+    /**
+     * Whether to convert the group output to lowercase
+     */
+    private boolean outputToLowerCase;
+
+    /**
+     * Whether to update last and previous login OS user properties (these are also used if using atlassian-user schema).
+     */
+    private boolean updateLastLogin;
+
+    /**
+     * Whether or not to automatically create groups.
+     */
+    private boolean autoCreateGroup;
+
+    /**
+     * Whether or not to convert username to lowercase before use
+     */
+    private boolean usernameConvertCase;
+
+    /**
+     * Whether or not web.xml has been configured to use ShibLoginFilter or not.
+     */
+    private boolean usingShibLoginFilter;
+
+    /**
+     * Should this pluggin try to create new groups as indicated
+     * by IdP (when the group value is non-existent in confluence)
+     *
+     * @param autoCreateGroup if true then new groups will be automatically
+     *                        created in confluence, otherwise they will be ignored
+     */
+    public void setAutoCreateGroup(boolean autoCreateGroup) {
+        this.autoCreateGroup = autoCreateGroup;
+    }
+
+    public boolean isAutoCreateGroup() {
+        return this.autoCreateGroup;
+    }
+
+    /**
+     * Given the key (header, e.g. SHIB-EP-ENTITLEMENT),
+     * return back the active group mappings that
+     * can handle the key
+     *
+     * @param key string to represent header
+     * @return group mappers registered to handle the key
+     */
+    public Collection getGroupMappings(String key) {
+        return (Collection) groupMappings.get(key);
+    }
+
+    public Collection getGroupMappings() {
+        return groupMappings.values();
+    }
+
+    public Set getGroupMappingKeys() {
+        return groupMappings.keySet();
+    }
+
+    public void setGroupMappings(Map mappings) {
+        groupMappings.clear();
+        groupMappings.putAll(mappings);
+    }
+
+    public void setPurgeMappings(Collection mappings) {
+        purgeMappings.clear();
+        purgeMappings.addAll(mappings);
+    }
+
+    public Collection getPurgeMappings() {
+        return purgeMappings;
+    }
+
+    public Collection getRemoteUserMappings() {
+        return remoteUserMappings;
+    }
+
+    public void setRemoteUserMappings(Collection mappings) {
+        remoteUserMappings.clear();
+        remoteUserMappings.addAll(mappings);
+    }
+
+    public void setRemoteUserReplacementChars(List replacements) {
+        remoteUserReplacementChars.clear();
+        remoteUserReplacementChars.addAll(replacements);
+    }
+
+    /**
+     * Iterator HAS to be processed pair-wise (e.g. entry 1 & 2)
+     * where entry 1 is the chars to be replaced (regex) and
+     * entry 2 is the replacement for it <bold>non-regex</bold>
+     * (null means total deletion).
+     *
+     * @return pair-wise iterator of replacement regex
+     */
+    public Iterator getRemoteUserReplacementChars() {
+        return remoteUserReplacementChars.iterator();
+    }
+
+    public Collection getFullNameMappings() {
+        return fullNameMappings;
+    }
+
+    public void setFullNameMappings(Collection mappings) {
+        fullNameMappings.clear();
+        fullNameMappings.addAll(mappings);
+    }
+
+    public void setFullNameReplacementChars(List replacements) {
+        fullNameReplacementChars.clear();
+        fullNameReplacementChars.addAll(replacements);
+    }
+
+    /**
+     * Iterator HAS to be processed pair-wise (e.g. entry 1 & 2)
+     * where entry 1 is the chars to be replaced (regex) and
+     * entry 2 is the replacement for it <bold>non-regex</bold>
+     * (null means total deletion).
+     *
+     * @return pair-wise iterator of replacement regex
+     */
+    public Iterator getFullNameReplacementChars() {
+        return fullNameReplacementChars.iterator();
+    }
+
+    public void setOutputToLowerCase(boolean outputToLowerCase) {
+        this.outputToLowerCase = outputToLowerCase;
+    }
+
+    public boolean isOutputToLowerCase() {
+        return outputToLowerCase;
+    }
+
+    public boolean isCreateUsers() {
+        return createUsers;
+    }
+
+    public void setCreateUsers(boolean createUsers) {
+        this.createUsers = createUsers;
+    }
+
+    public List getDefaultRoles() {
+        return defaultRoles;
+    }
+
+    public void setDefaultRoles(List defaultRoles) {
+        this.defaultRoles = defaultRoles;
+    }
+
+    public boolean isReloadConfig() {
+        return reloadConfig;
+    }
+
+    public void setReloadConfig(boolean reloadConfig) {
+        this.reloadConfig = reloadConfig;
+    }
+
+    public long getReloadConfigCheckInterval() {
+        return reloadConfigCheckInterval;
+    }
+
+    public void setReloadConfigCheckInterval(long reloadConfigCheckInterval) {
+        this.reloadConfigCheckInterval = reloadConfigCheckInterval;
+    }
+
+    public String getConfigFile() {
+        return configFile;
+    }
+
+    public void setConfigFile(String configFile) {
+        this.configFile = configFile;
+    }
+
+    public long getConfigFileLastModified() {
+        return configFileLastModified;
+    }
+
+    public void setConfigFileLastModified(long configFileLastModified) {
+        this.configFileLastModified = configFileLastModified;
+    }
+
+    public long getConfigFileLastChecked() {
+        return configFileLastChecked;
+    }
+
+    public void setConfigFileLastChecked(long configFileLastChecked) {
+        this.configFileLastChecked = configFileLastChecked;
+    }
+
+    public String getRemoteUserHeaderName() {
+        return remoteUserHeaderName;
+    }
+
+    public void setRemoteUserHeaderName(String remoteUserHeaderName) {
+        this.remoteUserHeaderName = remoteUserHeaderName;
+    }
+
+    public String getEmailHeaderName() {
+        return emailHeaderName;
+    }
+
+    public void setEmailHeaderName(String emailHeaderName) {
+        this.emailHeaderName = emailHeaderName;
+    }
+
+    public String getFullNameHeaderName() {
+        return fullNameHeaderName;
+    }
+
+    public void setFullNameHeaderName(String fullNameHeaderName) {
+        this.fullNameHeaderName = fullNameHeaderName;
+    }
+
+    public boolean isUpdateInfo() {
+        return updateInfo;
+    }
+
+    public void setUpdateInfo(boolean updateInfo) {
+        this.updateInfo = updateInfo;
+    }
+
+    public boolean isUpdateRoles() {
+        return updateRoles;
+    }
+
+    public void setUpdateRoles(boolean updateRoles) {
+        this.updateRoles = updateRoles;
+    }
+
+    public boolean isConvertToUTF8() {
+        return convertToUTF8;
+    }
+
+    public void setConvertToUTF8(boolean convertToUTF8) {
+        this.convertToUTF8 = convertToUTF8;
+    }
+
+    public boolean isUpdateLastLogin() {
+        return updateLastLogin;
+    }
+
+    public void setUpdateLastLogin(boolean updateLastLogin) {
+        this.updateLastLogin = updateLastLogin;
+    }
+
+    public boolean isUsernameConvertCase() {
+        return usernameConvertCase;
+    }
+
+    public void setUsernameConvertCase(boolean usernameConvertCase) {
+        this.usernameConvertCase = usernameConvertCase;
+    }
+
+    public boolean isUsingShibLoginFilter() {
+        return usingShibLoginFilter;
+    }
+
+    public void setUsingShibLoginFilter(boolean usingShibLoginFilter) {
+        this.usingShibLoginFilter = usingShibLoginFilter;
+    }
+
+    /**
+     * Given a prefix and list of strings, grab all those that started
+     * with 'prefix'.
+     *
+     * @param strings complete lists of all strings
+     * @param prefix  the prefix that we're looking for in a string
+     * @return subset of strings that started with the given prefix
+     */
+    public List listPostfixes(String[] strings, String prefix) {
+        List list = new ArrayList();
+
+        for (int i = 0; i < strings.length; i++) {
+            if (strings[i].startsWith(prefix)) {
+                String header = strings[i].substring(prefix.length());
+                if (header.length() != 0) {
+                    list.add(header);
+                }
+            }
+        }
+
+        return list;
+    }
+}
diff --git a/src/main/java/net/shibboleth/tools/jira/authn/ShibAuthConstants.java b/src/main/java/net/shibboleth/tools/jira/authn/ShibAuthConstants.java
new file mode 100644
index 0000000..3c491ea
--- /dev/null
+++ b/src/main/java/net/shibboleth/tools/jira/authn/ShibAuthConstants.java
@@ -0,0 +1,182 @@
+/*
+ Copyright (c) 2008-2012, Shibboleth Authenticator for Confluence Team
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ * Neither the name of the Shibboleth Authenticator for Confluence Team
+   nor the names of its contributors may be used to endorse or promote
+   products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package net.shibboleth.tools.jira.authn;
+
+
+public class ShibAuthConstants {
+
+    /**
+     * Location of configuration file on classpath
+     */
+    public final static String PROPERTIES_FILE = "/remoteUserAuthenticator.properties";
+
+    /**
+     * create.user init parameter name
+     */
+    public final static String CREATE_USERS = "create.users";
+
+    /**
+     * default.role init parameter name
+     */
+    public final static String DEFAULT_ROLES = "default.roles";
+
+    /**
+     * purge.roles init parameter name
+     */
+    public final static String PURGE_ROLES = "purge.roles";
+
+    /**
+     * reload.config init parameter name
+     */
+    public final static String RELOAD_CONFIG = "reload.config";
+
+    /**
+     * reload.config.check.interval init parameter name
+     */
+    public final static String RELOAD_CONFIG_CHECK_INTERVAL = "reload.config.check.interval";
+
+    /**
+     * Name of username header property
+     */
+    public final static String REMOTE_USER_HEADER_NAME_PROPERTY = "header.remote_user";
+
+    /**
+     * Name of email address header property
+     */
+    public final static String EMAIL_HEADER_NAME_PROPERTY = "header.email";
+
+    /**
+     * Name of full name header property
+     */
+    public final static String FULLNAME_HEADER_NAME_PROPERTY = "header.fullname";
+
+    /**
+     * Name of username case conversion property
+     */
+    public final static String USERNAME_CASE_CONVERT_PROPERTY = "username.convertcase";
+
+    /**
+     * Prefix to be used for remote-user label
+     * <p/>
+     * See #REMOTEUSER_PREFIX
+     */
+    public final static String REMOTEUSER_PREFIX = "remoteuser";
+
+    /**
+     * Prefix to be used for remote-user replacement list
+     * <p/>
+     * See #REMOTEUSER_REPLACEMENT
+     */
+    public final static String REMOTEUSER_REPLACEMENT = "remoteuser.replace";
+
+    /**
+     * Prefix to be used for remote-user mapping i.e.
+     * remoteuser.map.LABEL1.match=regex
+     * <p/>
+     * See #REMOTEUSER_MAP_PREFIX
+     */
+    public final static String REMOTEUSER_MAP_PREFIX = "remoteuser.map.";
+
+    /**
+     * Prefix to be used for mapping of different roles. i.e.
+     * dynamicroles.header.SHIB-EP-ENTITLEMENT=label1, label2
+     * <p/>
+     * See #ROLES_ATTRIB_PREFIX
+     */
+    public final static String ROLES_HEADER_PREFIX = "dynamicroles.header.";
+
+    /**
+     * Convert all groups output into lowercase before creating them in
+     * confluence. This is useful to handle confluence's bug of not allowing
+     * group names to be in uppercase
+     */
+    public final static String ROLES_OUTPUT_TOLOWER = "dynamicroles.output.tolowercase";
+
+    /**
+     * Prefix to be used for mapping of different roles. i.e.
+     * dynamicroles.mapper.label1.match=regex
+     * dynamicroles.mapper.label1.transform= group1, group2, $2
+     */
+    public final static String ROLES_ATTRIB_PREFIX = "dynamicroles.mapper.";
+
+    /**
+     * Label to represent indicate whether the group be automatically created
+     * when the IdP provides new group non-existent in confluence.
+     */
+    public final static String AUTO_CREATE_GROUP = "dynamicroles.auto_create_role";
+
+    public final static String PART_MATCH = ".match";
+    public final static String PART_TRANSFORM = ".transform";
+    public final static String PART_SENSITIVE = ".casesensitive";
+
+    /**
+     * update.info init parameter name
+     */
+    public final static String UPDATE_INFO = "update.info";
+
+    /**
+     * update.last.login.date init parameter name
+     */
+    public final static String UPDATE_LAST_LOGIN_DATE = "update.last.login.date";
+
+    /**
+     * update.roles init parameter name
+     */
+    public final static String UPDATE_ROLES = "update.roles";
+
+    /**
+     * convert.to.utf8 init parameter name
+     */
+    public final static String CONVERT_TO_UTF8 = "convert.to.utf8";
+
+    public final static String USING_SHIB_LOGIN_FILTER = "using.shib.login.filter";
+
+    /**
+     * Prefix to be used for remote-user label
+     * <p/>
+     * See #FULL_NAME_PREFIX
+     */
+    public final static String FULL_NAME_PREFIX = "fullname";
+
+    /**
+     * Prefix to be used for remote-user replacement list
+     * <p/>
+     * See #FULL_NAME_REPLACEMENT
+     */
+    public final static String FULL_NAME_REPLACEMENT = "fullname.replace";
+
+    /**
+     * Prefix to be used for remote-user mapping i.e.
+     * fullname.map.LABEL1.match=regex
+     * <p/>
+     * See #FULL_NAME_MAP_PREFIX
+     */
+    public final static String FULL_NAME_MAP_PREFIX = "fullname.map.";
+}
diff --git a/src/main/java/net/shibboleth/tools/jira/authn/StringUtil.java b/src/main/java/net/shibboleth/tools/jira/authn/StringUtil.java
new file mode 100644
index 0000000..8b4fe0b
--- /dev/null
+++ b/src/main/java/net/shibboleth/tools/jira/authn/StringUtil.java
@@ -0,0 +1,135 @@
+/*
+ Copyright (c) 2008-2012, Shibboleth Authenticator for Confluence Team
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+ * Neither the name of the Shibboleth Authenticator for Confluence Team
+   nor the names of its contributors may be used to endorse or promote
+   products derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package net.shibboleth.tools.jira.authn;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.util.*;
+
+
+public class StringUtil {
+
+    private final static Log log = LogFactory.getLog(StringUtil.class);
+
+    /**
+     * Define split characters *
+     */
+    private final static String SEPARATOR = "[,;]";
+
+    public static List toListOfNonEmptyStringsDelimitedByCommaOrSemicolon(String s) {
+        if (s == null) return Collections.EMPTY_LIST;
+
+        List results = new ArrayList();
+
+        String[] terms = s.split(SEPARATOR);
+
+        for (int i = 0; i < terms.length; i++) {
+            String term = terms[i].trim();
+            if (term.length() > 0) {
+                results.add(term);
+            }
+        }
+
+        return results;
+    }
+
+    /**
+     * This version returns the complete list, including empty string, if such entry is empty
+     *
+     * @param s input string delimited by comma or semicolon
+     * @return list of strings where deliminators are stripped off. if no content
+     *         is found between 2 delimitors then empty string is returned in its place
+     */
+    public static List toListOfStringsDelimitedByCommaOrSemicolon(String s) {
+        if (s == null) {
+            return Collections.EMPTY_LIST;
+        }
+
+        List results = new ArrayList();
+        String[] terms = s.split(SEPARATOR);
+
+        for (int i = 0; i < terms.length; i++) {
+            //this has empty string if nothing is found
+            String term = terms[i].trim();
+            results.add(term);
+        }
+
+        return results;
+    }
+
+    // PLEASE DO NOT CHANGE THIS METHOD WITHOUT GETTING AGREEMENT FROM JUHA OJALUOMA AND ERKKI AALTO. THIS METHOD WAS
+    // CONTRIBUTED BY HELSINKI IN BOTH SHBL-5 AND AGAIN IN SHBL-29.
+    public static String convertToUTF8(String s) {
+        String converted = null;
+
+        if (s != null) {
+            byte[] bytes = new byte[s.length()];
+
+            for (int i = 0; i < s.length(); i++) {
+                bytes[i] = (byte) s.charAt(i);
+            }
+
+            try {
+                converted = new String(bytes, "UTF-8");
+                if (log.isDebugEnabled()) {
+                    log.debug("Converted '" + s + "' to UTF-8 '" + converted + "'.");
+                }
+            } catch (UnsupportedEncodingException ue) {
+                log.error("Unable to set UTF-8 character encoding for string '" + s + "'!", ue);
+            }
+        }
+
+        return converted;
+    }
+
+    /**
+     * Check if the collection has toCheck str in it, ignoring case sensitivity
+     *
+     * @param strings collection of strings
+     * @param toCheck the one to be checked
+     * @return
+     */
+    public static boolean containsStringIgnoreCase(Collection strings, String toCheck) {
+        if (toCheck == null) {
+            return false;
+        }
+
+        //TODO: Find better way of doing this comparison.
+        for (Iterator it = strings.iterator(); it.hasNext(); ) {
+            if (toCheck.equalsIgnoreCase((String) it.next())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/src/main/resources/applicationContext.xml b/src/main/resources/applicationContext.xml
new file mode 100644
index 0000000..5ee5d7b
--- /dev/null
+++ b/src/main/resources/applicationContext.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                           http://www.springframework.org/schema/beans/spring-beans.xsd">
+    <!-- autowire via setter injection. we're using this to allow loading of jar via classpath
+         vs. atlassian plugins v1 or v2, since loading by classpath in Confluence 3.5.x-4.x is still
+         recommended by Atlassian. -->
+
+    <bean id="remoteUserAuthenticator"
+          class="shibauth.confluence.authentication.shibboleth.RemoteUserAuthenticator"
+          autowire="byName"/>
+</beans>
\ No newline at end of file
diff --git a/src/test/java/shibauth/confluence/authentication/shibboleth/RemoteAuthenticatorTest.java b/src/test/java/shibauth/confluence/authentication/shibboleth/RemoteAuthenticatorTest.java
new file mode 100644
index 0000000..3ce4f54
--- /dev/null
+++ b/src/test/java/shibauth/confluence/authentication/shibboleth/RemoteAuthenticatorTest.java
@@ -0,0 +1,9 @@
+package shibauth.confluence.authentication.shibboleth;
+
+import junit.framework.TestCase;
+
+public class RemoteAuthenticatorTest extends TestCase {
+
+    public void testNoTests() {
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list