Passed
Push — master ( 0e8d40...97ce2e )
by Terrence
13:45
created

Skin::printSkinCSS()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 10
nc 3
nop 0
dl 0
loc 12
ccs 0
cts 11
cp 0
crap 12
rs 9.9332
c 1
b 0
f 0
1
<?php
2
3
namespace CILogon\Service;
4
5
use CILogon\Service\Util;
6
use tubalmartin\CssMin\Minifier as CSSmin;
7
8
/**
9
 * Skin
10
 *
11
 * This class reads in CSS and configuration options
12
 * for a 'skin'.  The skin is a named subdirectory under
13
 * /var/www/html/skin/ and is set by passing the
14
 * '?skin=...' (or '?cilogon_skin=...') URL parameter.
15
 * If found, this class verifies the existence of such
16
 * a named directory and reads the skin.css and config.xml
17
 * files.  It also sets a PHP session variable so that
18
 * the skin name is remembered across page loads.
19
 *
20
 * Note that this class uses the SimpleXML class to parse
21
 * the config.xml file.  This stores the XML in a special
22
 * SimpleXMLElement object, which is NOT an array.  But
23
 * you can iterate over elements in the structure.  See
24
 * the PHP SimpleXML online manual 'Basic Usage' for
25
 * more information.
26
 *
27
 * This class provides a getConfigOption() method to
28
 * access XML (sub)blocks to get at a config value.
29
 * It is important to rememeber that the values returned
30
 * by the getConfigOption() method must be typecast to
31
 * native datatypes in order to be used effectively.
32
 *
33
 * An example configuration file (with all available options) is at
34
 *     /var/www/html/skin/config-example.xml
35
 *
36
 * Example usage:
37
 *   require_once 'Skin.php';
38
 *   $skin = new Skin();
39
 *   // While outputting the <head> HTML block...
40
 *   $skin->printSkinCSS();
41
 *   // Get the value of a configuration option
42
 *   $idpwhitelist = $skin->getConfigOption('idpwhitelist');
43
 *   // Now, process entries in the $idpwhitelist
44
 *   if ((!is_null($idpwhitelist)) && (!empty($idpwhitelist->idp))) {
45
 *       foreach ($idpwhitelist->idp as $entityID) {
46
 *           echo '<p>' , (string)$entityID , "<\p>\n";
47
 *       }
48
 *   }
49
 *   // Check to see if <hideportalinfo> is set
50
 *   $hideportalinfo = false;
51
 *   $hpi=$skin->getConfigOption('portallistaction','hideportalinfo');
52
 *   if ((!is_null($hpi)) && ((int)$hpi > 0)) {
53
 *       $hideportalinfo = true;
54
 *   }
55
 */
56
class Skin
57
{
58
    /**
59
     * @var string $skinname The directory name of the skin
60
     */
61
    protected $skinname;
62
63
    /**
64
     * @var \SimpleXMLElement $configxml A SimpleXMLElement object for the
65
     *      config.xml file
66
     */
67
    protected $configxml;
68
69
    /**
70
     * @var array $forcearray An array of (URI,skinname) pairs for forcing
71
     *      skin application
72
     */
73
    protected $forcearray;
74
75
    /**
76
     *  __construct
77
     *
78
     * Default constructor. Calls init() to do the actual work.
79
     *
80
     * @return Skin A new Skin object.
81
     */
82
    public function __construct()
83
    {
84
        $this->init();
85
    }
86
87
    /**
88
     * init
89
     *
90
     * This function does the work of (re)initializing the skin object.
91
     * It finds the name of the skin (if any) and reads in the skin's
92
     * config.xml file (if present). Call this function to reset the
93
     * skin in case of possible forced skin due to IdP or portal
94
     * callbackURL.
95
     *
96
     * @param bool $reset True to reset the 'cilogon_skin' PHP session var to
97
     *        blank so that findSkinName doesn't check for it.
98
     *        Defaults to 'false'.
99
     */
100
    public function init($reset = false)
101
    {
102
        if ($reset) {
103
            Util::unsetSessionVar('cilogon_skin');
104
        }
105
106
        $this->forcearray = FORCE_SKIN_ARRAY;
0 ignored issues
show
Bug introduced by
The constant CILogon\Service\FORCE_SKIN_ARRAY was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
107
        $this->findSkinName();
108
        $this->readConfigFile();
109
        $this->setMyProxyInfo();
110
    }
111
112
    /**
113
     * findSkinName
114
     *
115
     * Get the name of the skin and store it in the class variable
116
     * $skinname.  This function checks for the name of the skin in
117
     * several places: (1) The FORCE_SKIN_ARRAY for a matching IdP
118
     * entityID or portal callbackURL, (2) in a URL parameter (can be
119
     * '?skin=', '?cilogon_skin=', '?vo='), (3) cilogon_vo form input
120
     * variable (POST for ECP case), or (4) 'cilogon_skin' PHP session
121
     * variable.  If it finds the skin name in any of these, it then
122
     * checks to see if such a named 'skin/...' directory exists on the
123
     * server. If so, it sets the class variable $skinname AND the
124
     * 'cilogon_skin' PHP session variable (for use on future page
125
     * loads by the user).
126
     *
127
     * Side Effect: Sets the 'cilogon_skin' session variable if needed.
128
     */
129
    public function findSkinName()
130
    {
131
        $this->skinname = '';
132
        $skinvar = '';
133
134
        // Check for matching IdP, callbackURI (OAuth1),
135
        // redirect_uri (OAuth2), or client_id (OAuth2)
136
        // in the FORCE_SKIN_ARRAY.
137
        $uristocheck = array(
138
            Util::getGetVar('redirect_uri'),
139
            Util::getGetVar('client_id'),
140
            Util::getSessionVar('callbackuri'),
141
            Util::getSessionVar('idp')
142
        );
143
144
        foreach ($uristocheck as $value) {
145
            if (strlen($value) > 0) {
146
                $skin = $this->getForceSkin($value);
147
                if (strlen($skin) > 0) {
148
                    $skinvar = $skin;
149
                    break;
150
                }
151
            }
152
        }
153
154
        // If no force skin found, check GET and POST parameters, as well as
155
        // previously set cilogon_skin PHP session variable.
156
        if (strlen($skinvar) == 0) {
157
            // First, look for '?skin=...'
158
            $skinvar = Util::getGetVar('skin');
159
        }
160
        if (strlen($skinvar) == 0) {
161
            // Next, look for '?cilogon_skin=...'
162
            $skinvar = Util::getGetVar('cilogon_skin');
163
        }
164
        if (strlen($skinvar) == 0) {
165
            // Next, look for '?vo=...'
166
            $skinvar = Util::getGetVar('vo');
167
        }
168
        if (strlen($skinvar) == 0) {
169
            // Next, check 'cilogon_vo' form input variable
170
            $skinvar = Util::getPostVar('cilogon_vo');
171
        }
172
        if (strlen($skinvar) == 0) {
173
            // Finally, check 'cilogon_skin' PHP session variable
174
            $skinvar = Util::getSessionVar('cilogon_skin');
175
        }
176
177
        // If we found $skinvar, check to see if a skin directory with that
178
        // name exists.  Loop through all skin directories so we can do a
179
        // case-insenstive comparison. If we find a match, set skinname.
180
        $found = false;
181
        if (strlen($skinvar) > 0) {
182
            $basedir = Util::getServerVar('DOCUMENT_ROOT') . '/skin';
183
            if ($handle = opendir($basedir)) {
184
                while ((false !== ($file = readdir($handle))) && (!$found)) {
185
                    if (
186
                        ($file != '.') && ($file != '..') &&
187
                        (is_dir($basedir . '/' . $file)) &&
188
                        (strcasecmp($skinvar, $file) == 0)
189
                    ) {
190
                        $this->skinname = $file;
191
                        Util::setSessionVar('cilogon_skin', $file);
192
                        $found = true;
193
                    }
194
                }
195
                closedir($handle);
196
            }
197
        }
198
        if (!$found) {
199
            Util::unsetSessionVar('cilogon_skin');
200
        }
201
    }
202
203
    /**
204
     * getSkinName
205
     *
206
     * This function returns the name of the skin.  Note that you must
207
     * call findSkinName to set the name of the skin.
208
     *
209
     * @return string The name of the skin stored in the protected class
210
     *         variable $skinname.
211
     */
212
    public function getSkinName()
213
    {
214
        return $this->skinname;
215
    }
216
217
    /**
218
     * readConfigFile
219
     *
220
     * This function looks for a file 'config.xml' in the skin
221
     * directory. If there is no skin specified, then it looks for the
222
     * 'default' config.xml file located at the top of the skin
223
     * directory. If either file is found, it reads it in and parses it
224
     * into the class variable $configxml. It uses SimpleXML to read in
225
     * the file which strips off the top-level <config> from the XML.
226
     */
227
    public function readConfigFile()
228
    {
229
        $this->configxml = null;
230
231
        /* Note that if $this->skinname is blank, then we are simply
232
         * reading the config.xml file at the top-level skin directory. */
233
        $skinconf = Util::getServerVar('DOCUMENT_ROOT') . '/skin/' .
234
            $this->skinname . '/config.xml';
235
        if (is_readable($skinconf)) {
236
            $xml = @simplexml_load_file($skinconf);
237
            if ($xml !== false) {
238
                $this->configxml = $xml;
239
            }
240
        }
241
    }
242
243
    /**
244
     * getconfigxml
245
     *
246
     * This function returns a SimpleXMLElement corresponding to the
247
     * contents of the skin's config.xml file.  Note that you should
248
     * call readConfigFile to set the contents of $configxml.
249
     *
250
     * @return \SimpleXMLElement The SimpleXMLElement object corresponding to
251
     *         the parsed in XML config file.
252
     */
253
    public function getconfigxml()
254
    {
255
        return $this->configxml;
256
    }
257
258
    /**
259
     * getConfigOption
260
     *
261
     * This method returns a SimpleXMLElement block corresponding to
262
     * the passed in arguments.  For example, to get the blacklist of
263
     * idps, call $idps = getConfigOption('idpblacklist') and then
264
     * iterate over $idps with foreach($idps as $idp) { ... }.  To get
265
     * a single subblock value such as the initial lifetime number for
266
     * the PKCS12 download option, call $life =
267
     * (int)getConfigOption('pkcs12','initiallifetime','number'). Note
268
     * that you should explicitly cast the values to int, string,
269
     * float, etc., when you use them.
270
     *
271
     * @param mixed $args Variable number of parameters corresponding to XML
272
     *              blocks (and possible sub-blocks).
273
     * @return \SimpleXMLElement|null A SimpleXMLElement corresponding to the
274
     *         passed-in XML option, or 'null' if no such option exists.
275
     */
276
    public function getConfigOption(...$args)
277
    {
278
        $retval = null;
279
        $numargs = count($args);
280
        if ($numargs > 0) {
281
            $retval = $this->configxml;
282
        }
283
        for ($i = 0; $i < $numargs; $i++) {
284
            $argval = $args[$i];
285
            if (empty($retval->$argval)) {
286
                $retval = null;
287
                break;
288
            } else {
289
                $retval = $retval->$argval;
290
            }
291
        }
292
        return $retval;
293
    }
294
295
    /**
296
     * printSkinCSS
297
     *
298
     * Call this function in the HTML <head> block to print out the
299
     * <style> tag for the internal CSS of the skin. The CSS is minified
300
     * to remove whitespace.
301
     */
302
    public function printSkinCSS()
303
    {
304
        if (strlen($this->skinname) > 0) {
305
            $skinfile = Util::getServerVar('DOCUMENT_ROOT') . '/skin/' .
306
                $this->skinname . '/skin.css';
307
            if (is_readable($skinfile)) {
308
                $inputcss = file_get_contents($skinfile);
309
                $cssmin = new CSSmin();
310
                $cssmin->removeImportantComments();
311
                $cssmin->setLineBreakPosition(255);
312
                $outputcss = $cssmin->run($inputcss);
313
                echo "<style>$outputcss</style>";
314
            }
315
        }
316
    }
317
318
    /**
319
     * hasIdpWhitelist
320
     *
321
     * This function checks for the presence of a <idpwhitelist> block
322
     * in the skin's config file.  There must be at least one <idp>
323
     * in the <idpwhitelist>.
324
     *
325
     * @return bool True if skin has a non-empty <idpwhitelist>.
326
     */
327
    public function hasIdpWhitelist()
328
    {
329
        $retval = false;  // Assume no <idpwhitelist> configured
330
        $idpwhitelist = $this->getConfigOption('idpwhitelist');
331
        if ((!is_null($idpwhitelist)) && (!empty($idpwhitelist->idp))) {
332
            $retval = true;
333
        }
334
        return $retval;
335
    }
336
337
    /**
338
     * hasIdpBlacklist
339
     *
340
     * This function checks for the presence of a <idpblacklist> block
341
     * in the skin's config file.  There must be at least one <idp>
342
     * in the <idpblacklist>.
343
     *
344
     * @return bool True if skin has a non-empty <idpblacklist>.
345
     */
346
    public function hasIdpBlacklist()
347
    {
348
        $retval = false;  // Assume no <idpblacklist> configured
349
        $idpblacklist = $this->getConfigOption('idpblacklist');
350
        if ((!is_null($idpblacklist)) && (!empty($idpblacklist->idp))) {
351
            $retval = true;
352
        }
353
        return $retval;
354
    }
355
356
    /**
357
     * idpWhitelisted
358
     *
359
     * This method checks to see if a given entityId of an IdP
360
     * is whitelisted. 'Whitelisted' in this case means either (a) the
361
     * entityId is in the skin's <idpwhitelist> list or (b) the skin
362
     * doesn't have a <idpwhitelist> at all.  In the second case, all
363
     * IdPs are by default 'whitelisted'.  If you want to find if an
364
     * IdP should be listed in the WAYF, use 'idpAvailable' which
365
     * checks the whitelist AND the blacklist.
366
     *
367
     * @param string $entityId The entityId of an IdP to check for
368
     *        whitelisting.
369
     * @return bool True if the given IdP entityId is in the skin's
370
     *         whitelist (or if the skin doesn't have a whitelist).
371
     */
372
    public function idpWhitelisted($entityId)
373
    {
374
        $retval = true;  // Assume the entityId is 'whitelisted'
375
        if ($this->hasIdpWhitelist()) {
376
            $idpwhitelist = $this->getConfigOption('idpwhitelist');
377
            $found = false;
378
            foreach ($idpwhitelist->idp as $whiteidp) {
379
                if ($entityId == ((string)$whiteidp)) {
380
                    $found = true;
381
                    break;
382
                }
383
            }
384
            if (!$found) {
385
                $retval = false;
386
            }
387
        }
388
        return $retval;
389
    }
390
391
    /**
392
     * idpBlacklisted
393
     *
394
     * This method checks to see if a given entityId of an IdP
395
     * appears in the skin's <idpblacklist>.
396
     *
397
     * @param string $entityId The entityId of an IdP to check for
398
     *        blacklisting.
399
     * @return bool True if the given IdP entityId is in the skin's
400
     *         blacklist.
401
     */
402
    public function idpBlacklisted($entityId)
403
    {
404
        $retval = false;  // Assume entityId is NOT in the idpblacklist
405
        if ($this->hasIdpBlacklist()) {
406
            $idpblacklist = $this->getConfigOption('idpblacklist');
407
            foreach ($idpblacklist->idp as $blackidp) {
408
                if ($entityId == ((string)$blackidp)) {
409
                    $retval = true;
410
                    break;
411
                }
412
            }
413
        }
414
        return $retval;
415
    }
416
417
    /**
418
     * idpAvailable
419
     *
420
     * This method combines idpWhitelisted and idpBlacklisted to return
421
     * a 'yes/no' for if a given IdP should be made available for
422
     * selection in the WAYF.  It first checks to see if the IdP is
423
     * whitelisted.  If not, it returns false. Otherwise, it then
424
     * checks if the IdP is blacklisted.  If not, it returns true.
425
     *
426
     * @param string $entityId The entityId of an IdP to check to see if it
427
     *        should be available in the WAYF.
428
     * @return bool True if the given IdP entityId is available to be
429
     *         selected in the WAYF.
430
     */
431
    public function idpAvailable($entityId)
432
    {
433
        $retval = false;   // Assume IdP is not available in the WAYF
434
        if (
435
            ($this->idpWhitelisted($entityId)) &&
436
            (!$this->idpBlacklisted($entityId))
437
        ) {
438
            $retval = true;
439
        }
440
        return $retval;
441
    }
442
443
    /**
444
     * setMyProxyInfo
445
     *
446
     * This method sets the 'myproxyinfo' PHP session variable.  The
447
     * variable has the form 'info:key1=value1,key2=value2,...' and is
448
     * passed to the 'myproxy-logon' command as part of the username
449
     * when fetching a credential.  The MyProxy server will do extra
450
     * processing based on the content of this 'info:...' tag.  If the
451
     * skinname is not empty, that is added to the info tag.  Also,
452
     * the apache REMOTE_ADDR is added.  For other key=value pairs that
453
     * get added, see the code below.
454
     */
455
    public function setMyProxyInfo()
456
    {
457
        $infostr = '';
458
459
        // Add the skinname if available
460
        if (strlen($this->skinname) > 0) {
461
            $infostr .= 'cilogon_skin=' . $this->skinname;
462
        }
463
464
        // Add the REMOTE_ADDR
465
        $remoteaddr = Util::getServerVar('REMOTE_ADDR');
466
        if (strlen($remoteaddr) > 0) {
467
            $infostr .= (strlen($infostr) > 0 ? ',' : '') .
468
                        "remote_addr=$remoteaddr";
469
        }
470
471
        // Add eppn, eptid, open_id, and oidc if available
472
        // Note that these values are lowercase after an update to make
473
        // them the same as those used by the dbService. BUT, MyProxy
474
        // expects the old versions. So this array maps the new lowercase
475
        // versions back into the old ones.
476
        $mpid = array(
477
            'eppn' => 'ePPN',
478
            'eptid' => 'ePTID',
479
            'open_id' => 'openidID',
480
            'oidc' => 'oidcID'
481
        );
482
        foreach (array('eppn','eptid','open_id','oidc') as $id) {
483
            $sessvar = Util::getSessionVar($id);
484
            if (strlen($sessvar) > 0) {
485
                $infostr .= (strlen($infostr) > 0 ? ',' : '') .
486
                    $mpid[$id] . "=" . $sessvar;
487
            }
488
        }
489
490
        // Finally, set the 'myproxyinfo' PHP session variable
491
        if (strlen($infostr) > 0) {
492
            Util::setSessionVar('myproxyinfo', 'info:' . $infostr);
493
        } else {
494
            Util::unsetSessionVar('myproxyinfo');
495
        }
496
    }
497
498
    /**
499
     * hasPortalList
500
     *
501
     * This function checks for the presence of a <portallist> block in
502
     * the skin's config file.  There must be at least one <portal> in
503
     * the <portallist>.
504
     *
505
     * @return bool True if skin has a non-empty <portallist>.
506
     */
507
    public function hasPortalList()
508
    {
509
        $retval = false;  // Assume no <portallist> configured
510
        $portallist = $this->getConfigOption('portallist');
511
        if ((!is_null($portallist)) && (!empty($portallist->portal))) {
512
            $retval = true;
513
        }
514
        return $retval;
515
    }
516
517
    /**
518
     * inPortalList
519
     *
520
     * This function takes in a 'callback' URL of a portal passed to
521
     * the CILogon Delegate service.  It then looks through the list
522
     * of <portal> patterns in the skin's <portallist>.  If the
523
     * callback URL matches any of these patterns, true is returned.
524
     * This is used to hide the 'Site Name / Site URL / Service URL'
525
     * box on the delegation WAYF page, for example.
526
     *
527
     * @param string $portalurl A 'callback' URL of a portal accessing the
528
     *        delegate service.
529
     * @return bool True if the callback URL matches one of the patterns
530
     *         in the skin's <portallist>.  False otherwise.
531
     */
532
    public function inPortalList($portalurl)
533
    {
534
        $retval = false;  // Assume the portalurl not a listed <portal>
535
        if ($this->hasPortalList()) {
536
            $portallist = $this->getConfigOption('portallist');
537
            foreach ($portallist->portal as $portalmatch) {
538
                if (preg_match(((string)$portalmatch), $portalurl)) {
539
                    $retval = true;
540
                    break;
541
                }
542
            }
543
        }
544
        return $retval;
545
    }
546
547
    /**
548
     * getForceSkin
549
     *
550
     * The FORCE_SKIN_ARRAY contains 'uripattern'=>'skinname' pairs
551
     * corresponding to IdP entityIDs, portal callbackurls, or client_ids
552
     * that should have a particular skin force-applied. This function
553
     * looks in the FORCE_SKIN_ARRAY for a pattern-matched URI and returns
554
     * the corresponding skin name if found. If not found, empty
555
     * string is returned.
556
     *
557
     * @param string $uri A URI to search for in the FORCE_SKIN_ARRAY.
558
     * @return string The skin name for the URI, or empty string if not
559
     *         found.
560
     */
561
    public function getForceSkin($uri)
562
    {
563
        $retval = '';  // Assume uri is not in FORCE_SKIN_ARRAY
564
565
        foreach ($this->forcearray as $key => $value) {
566
            if (preg_match($key, $uri)) {
567
                $retval = $value;
568
                break;
569
            }
570
        }
571
        return $retval;
572
    }
573
}
574