ATLAS Offline Software
Loading...
Searching...
No Matches
VP1Authenticator.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
3*/
4
6
8
9#include <QNetworkAccessManager>
10#include <QNetworkCookie>
11#include <QFile>
12#include <QFileInfo>
13#include <QUrl>
14#include <QBuffer>
15#include <QStringMatcher>
16
17#include <iostream>
18#include <QMetaType>
19
20//______________________ Imp ______________________________
22
23public:
24 Imp(VP1Authenticator*,const QString &);
25 ~Imp();
26
27 Imp (const Imp&) = delete;
28 Imp& operator= (const Imp&) = delete;
29
30 // Gets value of the HTML tag attribute. Returns an empty string if attribute not found
31 QString getTagAttributeVal(const QString& tag, const QString& attribute);
32
33 // Connect signals of the QNetworkReply to slots of the VP1Authenticator
34 void connectToAuthenticator(VP1Authenticator* authenticator);
35
36 // Display error message in the TextEdit box
37 void displayError(const QString& );
38
40 QNetworkAccessManager* m_netmanager{};
41 QNetworkReply* m_netreply{};
42 QFile* m_log{};
43
44 int stage{1};
47};
48
50 , const QString & fileInfoUrl)
51 : m_theClass(theClass)
52 , m_netmanager(new QNetworkAccessManager())
53 , m_netreply(0)
54 , m_log(0)
55 , stage(1)
56 , m_fileInfoUrl(fileInfoUrl)
57 , m_loginServer("login.cern.ch")
58{
59 #if defined BUILDVP1LIGHT
60 bool checkAuthEnabled = VP1QtUtils::expertSettingIsOn("expert","ExpertSettings/VP1_AUTH_ENABLELOG");
61 #else
62 bool checkAuthEnabled = VP1QtUtils::environmentVariableIsOn("VP1_AUTH_ENABLELOG");
63 #endif
64
65 if(checkAuthEnabled) {
66 QString logname("vp1live-auth.log");
67 if(QFileInfo(logname).exists() && !QFile(logname).remove())
68 std::cerr << "VP1Authenticator ERROR: Unable to remove old logfile" << std::endl;
69 else {
70 m_log = new QFile(logname);
71 if(!m_log->open(QIODevice::WriteOnly)) {
72 std::cerr << "VP1Authenticator ERROR: Unable to open new logfile for writing" << std::endl;
73 delete m_log;
74 m_log = 0;
75 }
76 }
77 }
78}
79
81{
82 // deleting m_netmanager is not my responsibility :)
83 delete m_netreply;
84
85 if(m_log) {
86 m_log->close();
87 delete m_log;
88 }
89}
90
91QString VP1Authenticator::Imp::getTagAttributeVal(const QString& tag, const QString& attribute)
92{
93 QStringMatcher startMatcher(attribute + "=\"",Qt::CaseInsensitive);
94 QStringMatcher endMatcher("\"");
95
96 int startPos = startMatcher.indexIn(tag);
97 if(startPos==-1)
98 return QString("");
99 else {
100 int endPos = endMatcher.indexIn(tag,startPos+startMatcher.pattern().size());
101 if(endPos==-1) // something is wrong with this tag
102 return QString("");
103 else
104 return tag.mid(startPos+startMatcher.pattern().size(),endPos-startPos-startMatcher.pattern().size());
105 }
106}
107
109{
110 connect(m_netreply, SIGNAL(finished()),
111 authenticator, SLOT(finished()));
112 connect(m_netreply, SIGNAL(error(QNetworkReply::NetworkError)),
113 authenticator, SLOT(error(QNetworkReply::NetworkError)));
114#ifndef QT_NO_SSL
115 connect(m_netreply, SIGNAL(sslErrors(const QList<QSslError>&)),
116 authenticator, SLOT(sslErrors(const QList<QSslError>&)));
117#endif
118}
119
120void VP1Authenticator::Imp::displayError(const QString & message)
121{
122 m_theClass->teditError->setVisible(true);
123 m_theClass->setFixedSize(400,350);
124 m_theClass->teditError->append(message);
125 m_theClass->inpPhr->clear();
126 m_theClass->inpPers->setEnabled(true);
127 m_theClass->inpPhr->setEnabled(true);
128 m_theClass->inpPhr->setFocus();
129 if(m_log) {
130 QByteArray logBa(QString(message+"\n").toStdString().c_str());
131 m_log->write(logBa);
132 }
133}
134
135//__________________________ Main Class __________________________________
136VP1Authenticator::VP1Authenticator(QWidget* parent, const QString & fileInfoUrl)
137 : QDialog(parent)
138 , m_d(new Imp(this,fileInfoUrl))
139{
140 setupUi(this);
141 setFixedSize(400,200);
142
143 // Configure GUI properties
144 teditError->setVisible(false);
145 teditError->setLineWrapMode(QTextEdit::NoWrap);
146 pbtnLogin->setDefault(true);
147
148 // Connect GUI signals to slots
149 connect(pbtnLogin,SIGNAL(clicked()),this,SLOT(loginClicked()));
150 connect(pbtnCancel,SIGNAL(clicked()),this,SLOT(reject()));
151
152#ifndef QT_NO_SSL
153 qRegisterMetaType<QList<QSslError> >("QList<QSslError>");
154#endif
155}
156
161
162//____________ Https/SSL slots________________
163
165{
166 QUrl fileinfoUrl(m_d->m_fileInfoUrl);
167 QNetworkRequest netrequest(fileinfoUrl);
168 m_d->m_netreply = m_d->m_netmanager->get(netrequest);
169 m_d->connectToAuthenticator(this);
170
171 return true;
172}
173
175{
176 QString message("\n\nVP1Authenticator done. STAGE " + QString::number(m_d->stage) + "\n");
177
178 QUrl redirectionUrl;
179 QList<QNetworkCookie> cookielist;
180
181 // ******** Collect some information for the log ********
182
183 // ** Headers
184 QVariant val = m_d->m_netreply->header(QNetworkRequest::SetCookieHeader);
185 if(val.type()==QVariant::Invalid) {
186 message += QString(" No set cookies\n");
187 } else if (!val.canConvert<QList<QNetworkCookie> >()){
188 message += QString(" Cannot convert to the list of cookies\n");
189 } else {
190 cookielist = val.value<QList<QNetworkCookie> >();
191 for(int ii=0; ii<cookielist.size(); ++ii) {
192 const QNetworkCookie& cookie = cookielist.at(ii);
193 message += (" Received cookie #" + QString::number(ii) + "\n");
194 message += (" *** Path: " + cookie.path() + "\n");
195 message += (" *** Domain: " + cookie.domain() + "\n");
196 message += (QString(" *** Secure: ") + (cookie.isSecure() ? "YES" : "NO") + "\n");
197 message += (QString(" *** Session: ") + (cookie.isSessionCookie() ? "YES" : "NO") + "\n");
198 message += (" *** Name: " + QString(cookie.name().constData()) + "\n");
199 message += (" *** Value: " + QString(cookie.value().constData()) + "\n");
200 }
201 }
202
203 val = m_d->m_netreply->header(QNetworkRequest::ContentTypeHeader);
204 if(val.type()==QVariant::Invalid) {
205 message += QString(" No ContentType\n");
206 } else if (!val.canConvert<QString>()) {
207 message += QString(" Cannot convert Content Type to String\n");
208 } else {
209 QString conttype = val.value<QString>();
210 message += QString(" Content type: " + conttype + "\n");
211 }
212
213 val = m_d->m_netreply->header(QNetworkRequest::ContentLengthHeader);
214 if(val.type()==QVariant::Invalid) {
215 message += QString(" No ContentLength\n");
216 } else if (!val.canConvert<int>()) {
217 message += QString(" Cannot convert Content Length to int\n");
218 } else {
219 int contlength = val.value<int>();
220 message += QString(" Content Length: " + QString::number(contlength) + "\n");
221 }
222
223 val = m_d->m_netreply->header(QNetworkRequest::LocationHeader);
224 if(val.type()==QVariant::Invalid) {
225 message += QString(" No Location\n");
226 } else if (!val.canConvert<QUrl>()) {
227 message += QString(" Cannot convert Content Length to QUrl\n");
228 } else {
229 QUrl url = val.value<QUrl>();
230 message += QString(" Location URL " + url.toString() + "\n");
231 }
232
233 // ** Attributes
234 val = m_d->m_netreply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
235 if(val.type()==QVariant::Invalid) {
236 message += QString(" No StatusCode Attribute\n");
237 } else if (!val.canConvert<int>()) {
238 message += QString(" Cannot convert StatusCode to int\n");
239 } else {
240 int sc = val.value<int>();
241 message += QString(" StatusCode : " +QString::number(sc) + "\n");
242 }
243
244 val = m_d->m_netreply->attribute(QNetworkRequest::RedirectionTargetAttribute);
245 if(val.type()==QVariant::Invalid) {
246 message += QString(" No Redirection Attribute\n");
247 } else if (!val.canConvert<QUrl>()) {
248 message += QString(" Cannot convert Redirection to QUrl\n");
249 } else {
250 redirectionUrl = val.value<QUrl>();
251 message += QString(" Redirection : " + redirectionUrl.toString() + "\n");
252 }
253
254 message += QString("\n HTML response >>>>>\n");
255
256 QByteArray logMessage(message.toStdString().c_str());
257
258 // ** Write reply to log **
259 QByteArray ba = m_d->m_netreply->readAll();
260 logMessage = logMessage.append(ba);
261 logMessage = logMessage.append("\n<<<<< HTML response\n\n\n");
262 if(m_d->m_log)
263 m_d->m_log->write(logMessage);
264
265
266 // *** Stage 5:
267 // Final response from the server. It may happen that the
268 // authentication was successfull, however the user is not
269 // authorized to access the requested resource
270 //
271 // Check that...
272 if(m_d->stage==5) {
273 QString replyBody(ba.data());
274 if(replyBody.contains("authorization failed", Qt::CaseInsensitive)) {
275 QString errMessage("Authorization Failed");
276 m_d->displayError(errMessage);
277 m_d->m_netmanager->deleteLater();
278 m_d->m_netmanager = new QNetworkAccessManager();
279 m_d->stage=1;
280 m_d->m_netreply=0;
281 return;
282 }
283
284 if(m_d->m_netreply->error()==QNetworkReply::NoError)
285 authenticationSuccessful(m_d->m_netmanager);
286 }
287
288 // ** Check for errors **
289 if(m_d->m_netreply->error()!=QNetworkReply::NoError) {
290 QString errMessage("Network error occured during authentication phase\n");
291 errMessage += QString("Error code " + QString::number((int)m_d->m_netreply->error()) + "\nExplanation on http://doc.trolltech.com/4.4/qnetworkreply.html#NetworkError-enum");
292 m_d->displayError(errMessage);
293 m_d->stage=1;
294 return;
295 }
296
297 // *** Stage 1:
298 // Received a reply redirecting us to login.cern.ch
299 // Take the redirection URL and issue GET request for it
300 if(m_d->stage==1) {
301 if(redirectionUrl.isEmpty() || redirectionUrl.host() != m_d->m_loginServer) {
302 QString errMessage("Wrong URL: " + m_d->m_fileInfoUrl + "\nPlease fix the URL and restart the job");
303 m_d->displayError(errMessage);
304 m_d->stage=1;
305 return;
306 } else {
307 QNetworkRequest netrequest(redirectionUrl);
308 m_d->m_netreply = m_d->m_netmanager->get(netrequest);
309 if(m_d->m_log) {
310 QByteArray baLog("Get request sent\n_______________________________________________________\n\n");
311 m_d->m_log->write(baLog);
312 }
313 m_d->connectToAuthenticator(this);
314 m_d->stage++;
315 return;
316 }
317 }
318
319 // *** Stage 2:
320 // Received authentication form
321 // Parse contents of the authentication form
322 // look for input tags and collect their attributes
323 if(m_d->stage==2) {
324 QString replyBody(ba.data());
325
326 QString newRequestBody("__EVENTTARGET=&__EVENTARGUMENT=&__LASTFOCUS=");
327
328 QStringMatcher inputStartMatcher("<input ",Qt::CaseInsensitive);
329 QStringMatcher inputEndMatcher("/>");
330
331 int inputStart = inputStartMatcher.indexIn(replyBody,0);
332
333 while(inputStart!=-1) {
334 int inputEnd = inputEndMatcher.indexIn(replyBody,inputStart+inputStartMatcher.pattern().size());
335 if(inputEnd==-1) // something is wrong with this tag
336 break;
337 else {
338 // Let's parse it
339 QString tag = replyBody.mid(inputStart,inputEnd-inputStart);
340 QString typeVal = m_d->getTagAttributeVal(tag,"type");
341 QString nameVal = m_d->getTagAttributeVal(tag,"name");
342 QString valueVal = m_d->getTagAttributeVal(tag,"value").replace(" ","+");
343
344 if(QString::compare(typeVal,"text",Qt::CaseInsensitive)==0)
345 valueVal = inpPers->text();
346 else if(QString::compare(typeVal,"password",Qt::CaseInsensitive)==0)
347 valueVal = inpPhr->text();
348
349 if(QString::compare(typeVal,"checkbox",Qt::CaseInsensitive)!=0) {
350 QByteArray encodedNameVal = QUrl::toPercentEncoding(nameVal);
351 if(QString::compare(typeVal,"submit",Qt::CaseInsensitive)==0
352 || QString::compare(typeVal,"text",Qt::CaseInsensitive)==0
353 || QString::compare(typeVal,"password",Qt::CaseInsensitive)==0 ) {
354 newRequestBody+=("&"+QString(encodedNameVal)+"="+valueVal);
355 } else {
356 QByteArray encodedValueVal = QUrl::toPercentEncoding(valueVal);
357 if(newRequestBody.size()!=0)
358 newRequestBody+="&";
359 newRequestBody+=(QString(encodedNameVal)+"="+QString(encodedValueVal));
360 }
361 }
362
363 // move to the next input
364 inputStart = inputStartMatcher.indexIn(replyBody,inputEnd+inputEndMatcher.pattern().size());
365 }
366 }
367
368 QByteArray newBody = newRequestBody.toUtf8();
369
370 QString logMessage = "New Request Length: " + QString::number(newBody.size()) + "\n";
371 logMessage += ("New Request Body:\n" + newRequestBody.replace(inpPhr->text(),"xxx") + "\n");
372
373 // Get form action
374 // !!! Hardwire this for now:
375 QString actionUrlString("https://"+m_d->m_loginServer);
376
377 QStringMatcher actionStartMatcher("action=\"",Qt::CaseInsensitive);
378 QStringMatcher actionEndMatcher("\"");
379 int actionStart = actionStartMatcher.indexIn(replyBody,0);
380 if(actionStart!=-1) {
381 int actionEnd = actionEndMatcher.indexIn(replyBody,actionStart+actionStartMatcher.pattern().size());
382 if(actionEnd!=-1)
383 actionUrlString += replyBody.mid(actionStart+actionStartMatcher.pattern().size(),actionEnd-actionStart-actionStartMatcher.pattern().size());
384 }
385
386 logMessage += ("New URL: " + actionUrlString + "\n");
387 QByteArray actionUrlBa(actionUrlString.toStdString().c_str());
388 QString actionUrlStringDecoded = QUrl::fromPercentEncoding(actionUrlBa);
389 logMessage += ("Decoded URL: " + actionUrlStringDecoded.replace("&amp;","&")
390 + "\n\nPost request sent\n_______________________________________________________\n\n");
391
392 // Send Post request:
393 QNetworkRequest netrequest(QUrl(actionUrlStringDecoded.replace("&amp;","&")));
394 netrequest.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
395 netrequest.setHeader(QNetworkRequest::ContentLengthHeader,newBody.size());
396 m_d->m_netreply = m_d->m_netmanager->post(netrequest,newBody);
397 if(m_d->m_log) {
398 QByteArray baLog(logMessage.toStdString().c_str());
399 m_d->m_log->write(baLog);
400 }
401 m_d->connectToAuthenticator(this);
402 m_d->stage++;
403 return;
404 }
405
406 // *** Stage 3:
407 // Received response from the autentication attempt
408 //
409 // Check if the authentication was successful. This can be done
410 // either of these two ways:
411 // 1. Check number of set cookies, if it = 0 then authentication failed
412 // 2. Look for 'Logon failure' string in the response body
413 // We implement the option #1
414 //
415 // If the authentication considered successfull then parse contents of the
416 // response, look for input tags and collect their attributes
417 // and compose a new POST request
418 if(m_d->stage==3) {
419
420 if(cookielist.size()==0) {
421 // Authentication failed
422 QString errMessage("Authentication failed, please try again\n");
423 m_d->displayError(errMessage);
424 m_d->stage=1;
425 return;
426 }
427
428 QString replyBody(ba.data());
429 QByteArray excludeFromEncoding(" ");
430 QString newRequestBody("");
431
432 QStringMatcher inputStartMatcher("<input ",Qt::CaseInsensitive);
433 QStringMatcher inputEndMatcher("/>");
434
435 int inputStart = inputStartMatcher.indexIn(replyBody,0);
436
437 while(inputStart!=-1) {
438
439 // It can happen that /> is in the middle of some string, for example it can
440 // be part of value="...". We need to take this into account as well
441 int inputEnd = inputStart;
442 int quoteCount = 1; // just to be able to enter the next loop
443 int quotePos = inputStart;
444 QStringMatcher quoteMatcher("\"");
445
446 while(quoteCount%2!=0) {
447 inputEnd = inputEndMatcher.indexIn(replyBody,quotePos);
448 if(inputEnd==-1)
449 break;
450 quoteCount = 0;
451 quotePos = inputStart;
452
453 while(true) {
454 quotePos = quoteMatcher.indexIn(replyBody,quotePos);
455 if(quotePos==-1||quotePos>inputEnd)
456 break;
457 quoteCount++;
458 quotePos++;
459 }
460 }
461
462 if(inputEnd==-1) // something is wrong with this tag
463 break;
464 else {
465 // Let's parse it
466 QString tag = replyBody.mid(inputStart,inputEnd-inputStart);
467 QString typeVal = m_d->getTagAttributeVal(tag,"type");
468 QString nameVal = m_d->getTagAttributeVal(tag,"name");
469 QString valueVal = m_d->getTagAttributeVal(tag,"value");
470
471 if(QString::compare(typeVal,"text",Qt::CaseInsensitive)==0)
472 valueVal = inpPers->text();
473 else if(QString::compare(typeVal,"password",Qt::CaseInsensitive)==0)
474 valueVal = inpPhr->text();
475
476 if(QString::compare(typeVal,"checkbox",Qt::CaseInsensitive)!=0 &&
477 QString::compare(typeVal,"submit",Qt::CaseInsensitive)!=0) {
478 QByteArray encodedNameVal = QUrl::toPercentEncoding(nameVal);
479 QString valueVal1 = valueVal.replace("&lt;","<");
480 QString valueVal2 = valueVal.replace("&quot;","\"");
481 QByteArray encodedValueVal = QUrl::toPercentEncoding(valueVal2,excludeFromEncoding);
482 if(newRequestBody.size()!=0)
483 newRequestBody+="&";
484 newRequestBody+=(QString(encodedNameVal)+"="+QString(encodedValueVal).replace(" ","+"));
485 }
486
487 // move to the next input
488 inputStart = inputStartMatcher.indexIn(replyBody,inputEnd+inputEndMatcher.pattern().size());
489 }
490 }
491
492 QByteArray newBody = newRequestBody.toUtf8();
493
494 QString logMessage = "New Request Length: " + QString::number(newBody.size()) + "\n";
495 logMessage += ("New Request Body:\n" + newRequestBody + "\n");
496
497 // Get form action
498 QString actionUrlString("");
499
500 QStringMatcher actionStartMatcher("action=\"",Qt::CaseInsensitive);
501 QStringMatcher actionEndMatcher("\"");
502 int actionStart = actionStartMatcher.indexIn(replyBody,0);
503 if(actionStart!=-1) {
504 int actionEnd = actionEndMatcher.indexIn(replyBody,actionStart+actionStartMatcher.pattern().size());
505 if(actionEnd!=-1)
506 actionUrlString = replyBody.mid(actionStart+actionStartMatcher.pattern().size(),actionEnd-actionStart-actionStartMatcher.pattern().size());
507 }
508
509 logMessage += ("New URL: " + actionUrlString + "\n");
510
511 // Send Post request:
512 QNetworkRequest netrequest;
513 netrequest.setUrl(actionUrlString);
514 netrequest.setHeader(QNetworkRequest::ContentTypeHeader,"application/x-www-form-urlencoded");
515 netrequest.setHeader(QNetworkRequest::ContentLengthHeader,newBody.size());
516 m_d->m_netreply = m_d->m_netmanager->post(netrequest,newBody);
517 logMessage += ("\n\nPost request sent\n_______________________________________________________\n\n");
518 if(m_d->m_log) {
519 QByteArray baLog(logMessage.toStdString().c_str());
520 m_d->m_log->write(baLog);
521 }
522 m_d->connectToAuthenticator(this);
523 m_d->stage++;
524 return;
525 }
526
527 // *** Stage 4:
528 // Recieved a final redirection to the requested resource
529 // Just try to get it
530 if(m_d->stage==4) {
531 QNetworkRequest netrequest(redirectionUrl);
532 m_d->m_netreply = m_d->m_netmanager->get(netrequest);
533 if(m_d->m_log) {
534 QByteArray baLog("Get request sent\n_______________________________________________________\n\n");
535 m_d->m_log->write(baLog);
536 }
537 m_d->connectToAuthenticator(this);
538 m_d->stage++;
539 return;
540 }
541}
542
543void VP1Authenticator::error(QNetworkReply::NetworkError err)
544{
545 if(m_d->m_log) {
546 QString message("VP1Authenticator error. STAGE " + QString::number(m_d->stage) + ", error code: " + QString::number((int)err) + "\n");
547 QByteArray ba(message.toStdString().c_str());
548 m_d->m_log->write(ba);
549 }
550}
551
552#ifndef QT_NO_SSL
553void VP1Authenticator::sslErrors(const QList<QSslError>& errlist)
554{
555 if(m_d->m_log) {
556 QString message("VP1Authenticator SSL errors. STAGE " + QString::number(m_d->stage) + "\n");
557 for(int ii=0; ii<errlist.size(); ++ii)
558 message += (" " + QString::number((int)errlist.at(ii).error()) + ", " + errlist.at(ii).errorString() + "\n");
559 QByteArray ba(message.toStdString().c_str());
560 m_d->m_log->write(ba);
561 }
562 m_d->m_netreply->ignoreSslErrors();
563}
564#endif
565
566//____________ GUI slots________________
568{
569 // Hide error box, if visible
570 if(teditError->isVisible()) {
571 teditError->setVisible(false);
572 setFixedSize(400,200);
573 teditError->clear();
574 }
575
576 // Set focus on the login box, if empty
577 if(inpPers->text().isEmpty()) {
578 inpPers->setFocus();
579 return;
580 }
581
582 // Set focus on the pass box, if empty
583 if(inpPhr->text().isEmpty()) {
584 inpPhr->setFocus();
585 return;
586 }
587
588 inpPers->setEnabled(false);
589 inpPhr->setEnabled(false);
591}
592
594{
595 return m_d->m_netmanager;
596}
static Double_t sc
QNetworkAccessManager * m_netmanager
void connectToAuthenticator(VP1Authenticator *authenticator)
Imp(const Imp &)=delete
QString getTagAttributeVal(const QString &tag, const QString &attribute)
Imp(VP1Authenticator *, const QString &)
Imp & operator=(const Imp &)=delete
void displayError(const QString &)
VP1Authenticator * m_theClass
QNetworkAccessManager * networkAccessManager()
VP1Authenticator(QWidget *, const QString &)
void error(QNetworkReply::NetworkError)
void authenticationSuccessful(QNetworkAccessManager *)
void sslErrors(const QList< QSslError > &)
static bool environmentVariableIsOn(const QString &name)
static bool expertSettingIsOn(const QString &type, const QString &name)
bool exists(const std::string &filename)
does a file exist