From 799658d77ba427fddf3aefcf7fea8a00b764a1b2 Mon Sep 17 00:00:00 2001 From: AngusWarren Date: Fri, 23 Sep 2016 17:43:33 +0800 Subject: [PATCH] Updated RemoteUserConfluenceAuth to 1.2 --- .../RemoteUserConfluenceAuth.properties | 17 ++++ RemoteUserConfluenceAuth/pom.xml | 2 +- .../confluence/RemoteUserConfluenceAuth.java | 84 +++++++++++------- builds/RemoteUserConfluenceAuth-1.2.jar | Bin 0 -> 2902 bytes builds/RemoteUserConfluenceAuth-1.2.tar.gz | Bin 0 -> 5842 bytes 5 files changed, 70 insertions(+), 33 deletions(-) create mode 100644 RemoteUserConfluenceAuth/RemoteUserConfluenceAuth.properties create mode 100644 builds/RemoteUserConfluenceAuth-1.2.jar create mode 100644 builds/RemoteUserConfluenceAuth-1.2.tar.gz diff --git a/RemoteUserConfluenceAuth/RemoteUserConfluenceAuth.properties b/RemoteUserConfluenceAuth/RemoteUserConfluenceAuth.properties new file mode 100644 index 0000000..c0b4026 --- /dev/null +++ b/RemoteUserConfluenceAuth/RemoteUserConfluenceAuth.properties @@ -0,0 +1,17 @@ +## This file can override some default behaviour if saved in +## WEB-INF/classes/RemoteUserConfluenceAuth.properties + +## If you're passing the username in an HTTP header, set the name here in +## lowercase. Leave blank to use the special REMOTE_USER header. +#header=x-proxy-username +#header= +header=x-forward-name + +## Use trustedhosts to specify specific hosts which are allowed to authenticate +## via HTTP headers. Leave blank to allow all hosts. It supports a comma +## separated list of IP addresses. It does not support subnets or ranges. +#trustedhosts=192.168.0.1,192.168.0.2 +#trustedhosts=192.168.0.1 +#trustedhosts=192.168.0.1,127.0.0.1 +#trustedhosts= +trustedhosts=10.1.1.100,127.0.0.1 diff --git a/RemoteUserConfluenceAuth/pom.xml b/RemoteUserConfluenceAuth/pom.xml index c5aef92..1b095d4 100644 --- a/RemoteUserConfluenceAuth/pom.xml +++ b/RemoteUserConfluenceAuth/pom.xml @@ -7,7 +7,7 @@ 4.0.0 anguswarren.confluence RemoteUserConfluenceAuth - 1.1 + 1.2 Angus Warren diff --git a/RemoteUserConfluenceAuth/src/main/java/anguswarren/confluence/RemoteUserConfluenceAuth.java b/RemoteUserConfluenceAuth/src/main/java/anguswarren/confluence/RemoteUserConfluenceAuth.java index b32f066..4b9d97c 100644 --- a/RemoteUserConfluenceAuth/src/main/java/anguswarren/confluence/RemoteUserConfluenceAuth.java +++ b/RemoteUserConfluenceAuth/src/main/java/anguswarren/confluence/RemoteUserConfluenceAuth.java @@ -1,5 +1,5 @@ /** - * Copyright 2011 Angus Warren + * Copyright 2016 Angus Warren * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,52 +17,72 @@ package anguswarren.confluence; import org.apache.log4j.Category; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Properties; import java.security.Principal; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import com.atlassian.core.util.ClassLoaderUtils; import com.atlassian.confluence.user.ConfluenceAuthenticator; -public class RemoteUserConfluenceAuth extends ConfluenceAuthenticator -{ +public class RemoteUserConfluenceAuth extends ConfluenceAuthenticator { private static final Category log = Category.getInstance(RemoteUserConfluenceAuth.class); - public Principal getUser(HttpServletRequest request, HttpServletResponse response) - { + public Principal getUser(HttpServletRequest request, HttpServletResponse response) { Principal user = null; - try - { - if(request.getSession() != null && request.getSession().getAttribute(ConfluenceAuthenticator.LOGGED_IN_KEY) != null) - { + try { + if (request.getSession() != null && request.getSession().getAttribute(ConfluenceAuthenticator.LOGGED_IN_KEY) != null) { log.debug("Session found; user already logged in"); user = (Principal) request.getSession().getAttribute(ConfluenceAuthenticator.LOGGED_IN_KEY); String username = user.getName(); user = getUser(username); - } - else - { - log.debug("Trying RemoteUserConfluenceAuth SSO"); - String remoteuser = request.getRemoteUser(); - log.debug("remote_user set to: " + remoteuser); - if(remoteuser != null) - { - String[] username = remoteuser.split("@"); - user = getUser(username[0]); - log.debug("Logging in with username: " + user); - request.getSession().setAttribute(ConfluenceAuthenticator.LOGGED_IN_KEY, user); - request.getSession().setAttribute(ConfluenceAuthenticator.LOGGED_OUT_KEY, null); - } - else - { - log.warn("remote_user is null"); - return null; + } else { + Properties p = new Properties(); + try { + InputStream iStream = ClassLoaderUtils.getResourceAsStream("RemoteUserConfluenceAuth.properties", this.getClass()); + p.load(iStream); + } catch (Exception e) { + log.debug("Exception loading propertie. The properties file is optional anyway, so this may not be an issues: " + e, e); + } + + String trustedhosts = p.getProperty("trustedhosts"); + if (trustedhosts != null) { + String ipAddress = request.getRemoteAddr(); + if (Arrays.asList(trustedhosts.split(",")).contains(ipAddress)) { + log.debug("IP found in trustedhosts."); + } else { + log.debug("IP not found in trustedhosts: " + ipAddress); + return null; } + } else { + log.debug("trustedhosts not configured. If you're using http headers, this may be a security issue."); + } + + String remoteuser = null; + String header = p.getProperty("header"); + if (header == null) { + log.debug("Trying REMOTE_USER for SSO"); + remoteuser = request.getRemoteUser(); + } else { + log.debug("Trying HTTP header '" + header + "' for SSO"); + remoteuser = request.getHeader(header); + } + + if (remoteuser != null) { + String[] username = remoteuser.split("@"); + user = getUser(username[0]); + log.debug("Logging in with username: " + user); + request.getSession().setAttribute(ConfluenceAuthenticator.LOGGED_IN_KEY, user); + request.getSession().setAttribute(ConfluenceAuthenticator.LOGGED_OUT_KEY, null); + } else { + log.debug("remote_user is null"); + return null; + } } - } - catch (Exception e) - { - log.warn("Exception: " + e, e); + } catch (Exception e) { + log.error("Exception: " + e, e); } return user; } - } diff --git a/builds/RemoteUserConfluenceAuth-1.2.jar b/builds/RemoteUserConfluenceAuth-1.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..f652a4fd006ffb68c2d95d062ee49a6ced4e36c8 GIT binary patch literal 2902 zcmbW3c{mj69>*oZL=s~eOAaB#xR!_+SjEywCH#@ALb8-{1Rv|M{4~PqOeav9q%?aX))y z#B?k;m{^#QFe_aJW0V2-pqq*5B$FAOjh&If{O>fg|5?m;l<~b7sf#i;fLU58A`MoN zJro37L9w3;t{~Uf^R~i5X;5WihM;JIykv~*5BQyh!|sbvnO1rf}SSFnKR}vB!%VJO!TKWdT%QLae13hqw~CuJAm(g8a!h6})$q{>w|?i%tuX z7^ju>&&=6hsi!#$9TYQEqWjvEwoRJNHW7YqAqtOj@3rY;+u0E3DS-lG&z{JJ#dOWU}5~1>GJ!%uSZ7YpEz#S+TW~FV_4k2?zjMqmn#Nrfx&zGW32r!1pV)ax&i)JMOTKiX}0!0BDO%NX;!A!18R~G zxT~#vAl~&>B?^ZVPxY0!qMRxT*Gn*_g<*RuRZ9}*im}J6*_i$XIXe@ zEN=2KP3hz*Zi{vq>%Em$nf7xn!K0GFZpk=Hi-yI`#k+H(Gb6hp2lVmku~TJiuaS9T z+-g(PTTv0lz+7BZIQbOK00qELpbwU^Ub3fo?f5|GC^|58-7&?ZT;5ITShQT00tOYE%L) z+RBTUnOR7;f;^p~{8>LBmt(V>81ErPv*m-xKOEMx^scj4;;+)3-xPJ8I6=rfQCCy)OII+g@-rc+&Dem^6Y7ef2_ofUJUYrb+rRQTj+5KSrUf;AbRp5;?!;jSL zp5}y(sXfCviXC(AeuEa zvT*4wQ2qiu=}P!-D%|#|&`C8Gvx2fa{b#CRR>$5(` zZt+|nqPt#tcuj4q8V3hXhMX)GOqH(AgPl)=z;?QYNIOBz^^o3ykWmhGLD*;+ zW!0e8X;Z|Bzh2wpQNX9=b=hAfJVOfX(@Q61w#Xva2k0taci|fOlXF2%tzfz`c^q{a ztC8Rb7<)&TzDJ(01@ZglhzJYDG4~D64_UvHEh8xhJ0OG873n(bdPQ;{(Y-9CnM;Fg z&(>e?s=ku8l-(yreaOGDLIv+i-tA8x5ZrF7@D{yIAq-&0Qzn-iyW+|l&8d^~uCA>h zMWg4JtyOnAI}PQ^LR7SLS1}Dz^{AyKDXRFkWZKuZ`Wb<>8TSj3oBWD^N~eSpo?U5D zQ*NT1pX;D_OC};ilrMq#fqX5o>~)7wIPLIbuG-!y?+SjeqSx%Sx^kjK*`!It3rV_I z(WC4p(M)31C(EMNoQi!a2$vs4TaLR5+=BSq&dwW*SX8M()NakXr6ZmPn*!QaXRUPn z)ICbaWS5Y^B(CTW-Omv4l8$l5fp}rOr!s7`cuIiOJKG(}ZNl^?dZ=qsV$-b@(yEqk z!zah7nOe@94G+syvPUN@E)&5mr68hn0Ez;t#von_hdU~(%_tK%@~xEd^ZwUlOp6^H zHi0`7a!pL*ycVV4R!Mo4C#HTvb9F`UG#AdzN)G=j^zP-73!zSe2z%$1U&A<8OknUa zY88{`J$YPBD1NP`)O^FOtX>yR9O>&>R%_tCZKQVN59fxKix1rz(I3Y^*R*Z$!E52~ zn}UCtSOXe)a6!mc@N;Uqn>GCm(e~1q&U4I{nA{TRxSw_lPcB*Bs93?ly4COnm{)YR%F87LuY zig#?=I9&rJ*+pExFHm+JH28BQzt0`ugy=b?=I}7S_=?lmM_F2*qjbs#N$0K`%fIgA zmmL<`u7rzKgl)(bxpTnMs|%FngYY*o90~z{7PLu;7|U3!`P@mTX4x!k8f_MDRQuW* zes-`P5iH#Zv2quc$X0M6sXnc^;bUq02S8fBH`ZFOZO%#5BeN8Ju-7Hou6k$;>6+7; zTBh5df3~kyNx^W2;A zx!P$?p}-uzZAtU-pL`Dx^LwmpblZ{0*OS-xM4?-3Au!QJUgQ^E{1<`SXq}M9O(3H+;sR;ra>U(M@tXCp`T7ky)%S8`E3+7e5^uY>A z16f`phBqUI*|t8gr3LMN=`yUDRy~NjXEI$$19BQcshbjKR*{p&JSGT>dc9w^1FxRK z7}LM8#|Rc{QdJUpI_UXU8BKME6AvT$H|?(avbq8~fCwMmCqsJ8oi7^_8USJwhp)@3 zP{9IPdG@p8f+5MQz~V8MwQdE~-R87BJV5>&*63Sf7}Q(BSMgF0CHfUs6I9(CNehz7)~(rG5yGg|A9XSAO0BMfsa9pe=j|bYz#2_E1*Zq@1@6B@wfs5 y?T(Yoj|weE3jYoCjw>*T=lIhY#Pe5tJEEiii+{}Etfv?g2*Z58V literal 0 HcmV?d00001 diff --git a/builds/RemoteUserConfluenceAuth-1.2.tar.gz b/builds/RemoteUserConfluenceAuth-1.2.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..ebac338b2e1a570b8113ee14cf2ee568444feef4 GIT binary patch literal 5842 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

#TZ@!YX*@JWAzj~ zOB9Mr3qV^1!Lw@lxw(m8lZsOd5{nW+%P4X(A?si~0~8V=J3CS#`cm>!Av-Lf1_0@# zywno#GKnH!iU%s=QbzWkt)Zono}rlqXoyk=!8XFJ3ZH%>bI>#^h8iwZ9Y6&jXkdV( cVW6|kC>RB!U=)mkQ81(d050bGoB&V&06fQLg8%>k literal 0 HcmV?d00001