2121 */
2222package org .exist .xquery .modules .persistentlogin ;
2323
24+ import org .checkerframework .checker .nullness .qual .Nullable ;
2425import org .exist .EXistException ;
2526import org .exist .dom .QName ;
2627import org .exist .security .AuthenticationException ;
3031import org .exist .xquery .*;
3132import org .exist .xquery .value .*;
3233
34+ import java .util .EnumSet ;
35+ import java .util .HashMap ;
36+ import java .util .Map ;
37+
3338/**
3439 * Functions to access the persistent login module.
3540 */
3641public class PersistentLoginFunctions extends UserSwitchingBasicFunction {
42+ private enum Fn {
43+ REGISTER ("register" ),
44+ LOGIN ("login" ),
45+ INVALIDATE ("invalidate" );
46+
47+ final static Map <QName , Fn > lookup = new HashMap <>();
48+ static {
49+ for (Fn fn : EnumSet .allOf (Fn .class )) {
50+ lookup .put (fn .getQName (), fn );
51+ }
52+ }
53+
54+ static Fn get (Function f ) {
55+ return lookup .get (f .getName ());
56+ }
57+
58+ private final QName qname ;
59+
60+ Fn (String name ) {
61+ qname = new QName (name , PersistentLoginModule .NAMESPACE , PersistentLoginModule .PREFIX );
62+ }
3763
38- public final static FunctionSignature signatures [] = {
64+ public QName getQName () { return qname ; }
65+ }
66+
67+ public final static FunctionSignature [] signatures = {
3968 new FunctionSignature (
40- new QName ( "register" , PersistentLoginModule . NAMESPACE , PersistentLoginModule . PREFIX ),
69+ Fn . REGISTER . getQName ( ),
4170 "Try to log in the user and create a one-time login token. The token can be stored to a cookie and used to log in " +
4271 "(via the login function) as the same user without " +
4372 "providing credentials. However, for security reasons the token will be valid only for " +
@@ -55,7 +84,7 @@ public class PersistentLoginFunctions extends UserSwitchingBasicFunction {
5584 new FunctionReturnSequenceType (Type .ITEM , Cardinality .ZERO_OR_MORE , "result of the callback function or the empty sequence" )
5685 ),
5786 new FunctionSignature (
58- new QName ( "login" , PersistentLoginModule . NAMESPACE , PersistentLoginModule . PREFIX ),
87+ Fn . LOGIN . getQName ( ),
5988 "Try to log in the user based on the supplied token. If the login succeeds, the provided callback function " +
6089 "is called with 4 arguments: $token as xs:string, $user as xs:string, $password as xs:string, $timeToLive as duration. " +
6190 "$token will be a new token which can be used for the next request. The old token is deleted." ,
@@ -67,7 +96,7 @@ public class PersistentLoginFunctions extends UserSwitchingBasicFunction {
6796 new FunctionReturnSequenceType (Type .ITEM , Cardinality .ZERO_OR_MORE , "result of the callback function or the empty sequence" )
6897 ),
6998 new FunctionSignature (
70- new QName ( "invalidate" , PersistentLoginModule . NAMESPACE , PersistentLoginModule . PREFIX ),
99+ Fn . INVALIDATE . getQName ( ),
71100 "Invalidate the supplied one-time token, so it can no longer be used to log in." ,
72101 new SequenceType []{
73102 new FunctionParameterSequenceType ("token" , Type .STRING , Cardinality .EXACTLY_ONE , "a valid one-time token" )
@@ -85,77 +114,59 @@ public PersistentLoginFunctions(final XQueryContext context, final FunctionSigna
85114 @ Override
86115 public void analyze (final AnalyzeContextInfo contextInfo ) throws XPathException {
87116 super .analyze (contextInfo );
88- this . cachedContextInfo = new AnalyzeContextInfo (contextInfo );
117+ cachedContextInfo = new AnalyzeContextInfo (contextInfo );
89118 }
90119
91120 @ Override
92121 public Sequence eval (final Sequence [] args , final Sequence contextSequence ) throws XPathException {
93- if (isCalledAs ("register" )) {
94- final String user = args [0 ].getStringValue ();
95- final String pass ;
96- if (!args [1 ].isEmpty ()) {
97- pass = args [1 ].getStringValue ();
98- } else {
99- pass = null ;
100- }
101- final DurationValue timeToLive = (DurationValue ) args [2 ].itemAt (0 );
102- final FunctionReference callback ;
103- if (!args [3 ].isEmpty ()) {
104- callback = (FunctionReference ) args [3 ].itemAt (0 );
105- } else {
106- callback = null ;
107- }
108- try {
109- return register (user , pass , timeToLive , callback );
110- } finally {
111- if (callback != null ) {
112- callback .close ();
113- }
114- }
115- } else if (isCalledAs ("login" )) {
116- final String token = args [0 ].getStringValue ();
117- final FunctionReference callback ;
118- if (!args [1 ].isEmpty ()) {
119- callback = (FunctionReference ) args [1 ].itemAt (0 );
120- } else {
121- callback = null ;
122- }
123- try {
124- return authenticate (token , callback );
125- } finally {
126- if (callback != null ) {
127- callback .close ();
128- }
129- }
130- } else {
131- PersistentLogin .getInstance ().invalidate (args [0 ].getStringValue ());
132- return Sequence .EMPTY_SEQUENCE ;
122+ switch (Fn .get (this )) {
123+ case REGISTER : return register (args );
124+ case LOGIN : return login (args );
125+ case INVALIDATE : return invalidate (args );
126+ default : throw new XPathException (this , ErrorCodes .ERROR , "Unknown function: " + getName ());
133127 }
134128 }
135129
136- private Sequence register (final String user , final String pass , final DurationValue timeToLive , final FunctionReference callback ) throws XPathException {
137- if (login (user , pass )) {
130+ private Sequence register (Sequence [] args ) throws XPathException {
131+ final String user = args [0 ].getStringValue ();
132+
133+ final String pass ;
134+ if (args [1 ].isEmpty ()) {
135+ pass = null ;
136+ } else {
137+ pass = args [1 ].getStringValue ();
138+ }
139+
140+ final DurationValue timeToLive = (DurationValue ) args [2 ].itemAt (0 );
141+
142+ try (FunctionReference callback = getCallBack (args [3 ])) {
143+ if (!authenticated (user , pass )) {
144+ return Sequence .EMPTY_SEQUENCE ;
145+ }
138146 final PersistentLogin .LoginDetails details = PersistentLogin .getInstance ().register (user , pass , timeToLive );
139- return callback (callback , null , details );
147+ return call (callback , null , details );
140148 }
141- return Sequence .EMPTY_SEQUENCE ;
142149 }
143150
144- private Sequence authenticate (final String token , final FunctionReference callback ) throws XPathException {
145- final PersistentLogin .LoginDetails data = PersistentLogin .getInstance ().lookup (token );
151+ private Sequence login (Sequence [] args ) throws XPathException {
152+ final String token = args [0 ].getStringValue ();
153+ try (FunctionReference callback = getCallBack (args [1 ])) {
154+ final PersistentLogin .LoginDetails data = PersistentLogin .getInstance ().lookup (token );
146155
147- if (data == null ) {
148- return Sequence .EMPTY_SEQUENCE ;
149- }
156+ if (data == null || !authenticated (data .getUser (), data .getPassword ())) {
157+ return Sequence .EMPTY_SEQUENCE ;
158+ }
159+ return call (callback , token , data );
150160
151- if (login (data .getUser (), data .getPassword ())) {
152- return callback (callback , token , data );
153161 }
162+ }
154163
164+ private static Sequence invalidate (Sequence [] args ) throws XPathException {
165+ PersistentLogin .getInstance ().invalidate (args [0 ].getStringValue ());
155166 return Sequence .EMPTY_SEQUENCE ;
156167 }
157168
158- private boolean login (final String user , final String pass ) throws XPathException {
169+ private boolean authenticated (final String user , final String pass ) {
159170 try {
160171 final SecurityManager sm = BrokerPool .getInstance ().getSecurityManager ();
161172 final Subject subject = sm .authenticate (user , pass );
@@ -169,7 +180,7 @@ private boolean login(final String user, final String pass) throws XPathExceptio
169180 }
170181 }
171182
172- private Sequence callback ( final FunctionReference func , final String oldToken , final PersistentLogin .LoginDetails details ) throws XPathException {
183+ private Sequence call ( @ Nullable final FunctionReference func , final String oldToken , final PersistentLogin .LoginDetails details ) throws XPathException {
173184 final Sequence [] args = new Sequence [4 ];
174185 final String newToken = details .toString ();
175186
@@ -185,4 +196,11 @@ private Sequence callback(final FunctionReference func, final String oldToken, f
185196 func .analyze (cachedContextInfo );
186197 return func .evalFunction (null , null , args );
187198 }
199+
200+ private @ Nullable FunctionReference getCallBack (final Sequence arg ) {
201+ if (arg .isEmpty ()) {
202+ return null ;
203+ }
204+ return (FunctionReference ) arg .itemAt (0 );
205+ }
188206}
0 commit comments