001package votorola.a.web.wic.authen; // Copyright 2008-2010, 2012, Michael Allan.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE.
002
003import java.sql.SQLException;
004import javax.mail.internet.*;
005import org.apache.wicket.AttributeModifier;
006import org.apache.wicket.markup.html.basic.*;
007import org.apache.wicket.markup.html.form.*;
008import org.apache.wicket.model.*;
009import org.apache.wicket.request.cycle.*;
010import org.apache.wicket.validation.*;
011import org.openid4java.discovery.*;
012import votorola.a.*;
013import votorola.g.locale.*;
014import votorola.a.trust.*;
015import votorola.a.voter.*;
016import votorola.a.web.wic.*;
017import votorola.g.*;
018import votorola.g.lang.*;
019import votorola.g.logging.*;
020import votorola.g.mail.*;
021import votorola.g.web.wic.*;
022
023
024/** A page for the authentication of a user's email address, step 1.  On this page, the
025  * user inputs the email address to authenticate.
026  *
027  *     @see <a href='../../../../../../../a/web/wic/authen/WP_EmailAuthen1.html' target='_top'
028  *                                                        >WP_EmailAuthen1.html</a>
029  */
030@ThreadRestricted("wicket") final class WP_EmailAuthen1 extends VPageHTML
031{
032
033
034    /** Either logs the user in following on a successful OpenID authentication, or
035      * constructs a WP_EmailAuthen1 in order to continue the login attempt.
036      *
037      *     @param verifiedID the {@linkplain
038      *       org.openid4java.consumer.VerificationResult#getVerifiedId() verified
039      *       identifier} from the positive authentication result, per {@linkplain
040      *       UserSettings#setOpenID(Identifier) setOpenID}(verifiedID).
041      *     @param runner the request cycle runner to handle any post-authentication
042      *       processing.
043      *
044      *     @return null if the user is logged in as a result; otherwise a new instance of
045      *       WP_EmailAuthen1.
046      *
047      *     @see WP_OpenIDLogin#isPersistent()
048      *     @see WP_OpenIDLogin#isReauthenticationRequested()
049      */
050    static WP_EmailAuthen1 login( final Identifier verifiedID, final boolean persistent,
051      final boolean reauthenticationRequested, final RequestCycleRunner runner )
052    {
053        final String openID = verifiedID.getIdentifier();
054        if( !reauthenticationRequested )
055        {
056            final VRequestCycle cycle = VRequestCycle.get();
057            final VoteServer.Run vsRun = VOWicket.get().vsRun();
058            final UserSettings settings;
059            try
060            {
061                settings = UserSettings.forOpenID( openID, vsRun.userTable() );
062            }
063            catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); }
064
065            if( settings != null )
066            {
067                WP_OpenIDLogin.setUserInSession( settings.voterEmail(), "OpenID", persistent,
068                  cycle );
069                return null;
070            }
071            // else OpenID not yet associated, proceed with email authentication:
072        }
073
074        return new WP_EmailAuthen1( openID, persistent, new OpenIDSetter( verifiedID, runner ));
075    }
076
077
078
079    private WP_EmailAuthen1( final String openID, boolean _persistent, final RequestCycleRunner rcr )
080    {
081        persistent = _persistent;
082        runner = rcr;
083        final VRequestCycle cycle = VRequestCycle.get();
084        final BundleFormatter bun = cycle.bunW();
085
086        add( new Label( "title", bun.l( "a.web.wic.authen.WP_EmailAuthen1" ) ));
087        add( new Label( "explanation", bun.l( "a.web.wic.authen.WP_EmailAuthen1.explanation" )));
088
089        final Form<Void> y = new EmailAddressForm();
090        add( y );
091
092      // OpenID
093      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
094        y.add( new Label( "openIDLabel",
095          bun.l( "a.web.wic.authen.WP_Login.openid_identifier" )));
096        final TextField<String> openidField = new TextField<String>( "openID", new Model<String>( openID ));
097        openidField.setEnabled( false );
098        y.add( openidField );
099
100        y.add( new Label( "openIDDescription",
101          bun.l( "a.web.wic.authen.WP_EmailAuthen1.openIDDescription" )));
102
103      // Email
104      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
105        y.add( new Label( "userEmailLabel", bun.l( "a.web.wic.authen.WP_EmailAuthen1.userEmail" )));
106        final TextField<String> emailField = new TextField<String>( "userEmail", new PropertyModel<String>(
107          WP_EmailAuthen1.this, "userEmailInput" ));
108        invalidStyled( inputLengthConstrained( emailField ));
109        emailField.setRequired( true );
110        emailField.add( new WicEmailAddressValidator() // extracting conversions from a validator / this is improper / consider instead using a converter / cf. votorola.g.util.regex.WicPatternConverter
111        {
112            public @Override void validate( IValidatable<String> v )
113            {
114                claimedUserIAddress = null; // till proven otherwise
115                super.validate( v );
116            }
117            public @Override void onSuccess( InternetAddress iAddress )
118            {
119                claimedUserIAddress = iAddress;
120                InternetAddressX.canonicalize( claimedUserIAddress );
121            }
122        });
123        y.add( emailField );
124
125        y.add( new Label( "userEmailDescription",
126          bun.l( "a.web.wic.authen.WP_EmailAuthen1.userEmailDescription" )));
127
128      // Buttons
129      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
130        {
131            final Button button = new Button( "submit" );
132            button.add( AttributeModifier.replace( "value",
133              bun.l( "a.web.wic.authen.WP_EmailAuthen1.submit" )));
134            y.add( button );
135        }
136        {
137            final Button button = new Button( "submit-cancel" )
138            {
139                public @Override void onSubmit()
140                {
141                    super.onSubmit();
142                    runner.run( VRequestCycle.get() );
143                }
144            };
145            button.add( AttributeModifier.replace( "value",
146              bun.l( "a.web.wic.authen.WP_EmailAuthen1.submit-cancel" )));
147            button.setDefaultFormProcessing( false );
148            y.add( button );
149        }
150
151      // Feedback Messages
152      // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
153        add( new WC_Feedback( "feedback" ));
154
155    }
156
157
158
159//// P r i v a t e ///////////////////////////////////////////////////////////////////////
160
161
162    private transient InternetAddress claimedUserIAddress; // from field validator, in canonical form
163
164
165
166    private final boolean persistent;
167
168
169
170    private final RequestCycleRunner runner;
171
172
173
174    private String userEmailInput; // PropertyModel accesses it by java.lang.reflect.Field.setAccessible()
175
176
177
178   // ====================================================================================
179
180
181    private class EmailAddressForm extends Form<Void>
182    {
183
184        EmailAddressForm() { super( "form" ); }
185
186
187        protected @Override void onSubmit()
188        {
189            super.onSubmit();
190            if( claimedUserIAddress == null ) throw new IllegalStateException();
191
192            final VRequestCycle cycle = VRequestCycle.get();
193            final WP_EmailAuthen2 authenticationPage =
194              new WP_EmailAuthen2( claimedUserIAddress, persistent, cycle, runner );
195            cycle.setResponsePage( authenticationPage.sendMessage( cycle )?
196              authenticationPage: new WP_Message() );
197        }
198
199
200    }
201
202
203
204   // ====================================================================================
205
206
207    private static final class OpenIDSetter extends RequestCycleRunnerW
208    {
209
210        private static final long serialVersionUID = 1L;
211
212
213        private OpenIDSetter( final Identifier verifiedID, RequestCycleRunner runner )
214        {
215            super( runner );
216            this.verifiedID = verifiedID;
217        }
218
219
220        private final VSession.User userAtAutheticationTime = VSession.get().user();
221           // null, unless user already logged in
222
223
224        private final Identifier verifiedID;
225
226
227        public @Override void run( RequestCycle wCycle )
228        {
229            final VRequestCycle cycle = (VRequestCycle)wCycle;
230            final VSession.User user = VSession.get().user();
231            if( user != null && user != userAtAutheticationTime ) // != intended
232            {
233                // login went to completion, not cancelled
234                final UserSettings.Table table = VOWicket.get().vsRun().userTable();
235                try
236                {
237                    UserSettings settings = UserSettings.forOpenID(
238                      verifiedID.getIdentifier(), table );
239                    if( settings != null && !settings.voterEmail().equals( user.email() ))
240                    {
241                        table.delete( settings.voterEmail() ); // remove association with old email address
242                        settings = null;
243                    }
244
245                    if( settings == null )
246                    {
247                        settings = new UserSettings( user.email(), table );
248                        settings.setOpenID( verifiedID ); // associate this authenticated OpenID with the user, for next login
249                        settings.write( table, VSession.get() );
250                    }
251                }
252                catch( Exception x ) { throw VotorolaRuntimeException.castOrWrapped( x ); }
253            }
254            super.run( wCycle );
255        }
256    }
257
258
259
260}