diff --git a/.htaccess b/.htaccess index 6000d85..340b4d1 100644 --- a/.htaccess +++ b/.htaccess @@ -1,7 +1,7 @@ -# RewriteEngine On -# RewriteCond %{HTTPS} !=on -# RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] + RewriteEngine On + RewriteCond %{HTTPS} !=on + RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] RewriteEngine On diff --git a/alter_tables.sql b/alter_tables.sql new file mode 100644 index 0000000..22fec2a --- /dev/null +++ b/alter_tables.sql @@ -0,0 +1,2 @@ +ALTER TABLE `room_booking`.`rooms` +ADD COLUMN `minimum_slot` INT(11) NULL DEFAULT 30 COMMENT '' AFTER `requires_moderation`; \ No newline at end of file diff --git a/application/config/autoload.php b/application/config/autoload.php index 9b402a8..4076bd7 100644 --- a/application/config/autoload.php +++ b/application/config/autoload.php @@ -109,7 +109,7 @@ | */ -$autoload['model'] = array(); +$autoload['model'] = array('settings_model'); /* End of file autoload.php */ diff --git a/application/config/constants.php.sample b/application/config/constants.php.sample index c1dac89..7efd875 100644 --- a/application/config/constants.php.sample +++ b/application/config/constants.php.sample @@ -38,14 +38,11 @@ define('FOPEN_READ_WRITE_CREATE_STRICT', 'x+b'); define('USER_AGENT', 'LibraryBooking/1.0'); -define('SITE_TITLE', 'Name goes here'); -define('SITE_LOGO', 'https://domain.com/assets/images/logo.png'); //Defaults if user has no roles define('DEFAULT_TEMPLATE', 'rula_template'); define('DEFAULT_POLICY_URL', 'https://domain.com/room-booking-policy/'); -define('DEBUG_MODE', FALSE); define('SITE_ADMIN', 'siteadmin@domain.com'); define('REPLY_EMAIL', 'contact@domain.com'); //Used as the "FROM" address when sending emails define('CONTACT_EMAIL', 'contact@domain.com'); @@ -80,6 +77,7 @@ define('TIME_DELAY', 0); //No delay define('SEND_MODERATION_ACTION_EMAIL', TRUE); define('SEND_MODERATION_REQUEST_CONFIRMATION_EMAIL', TRUE); +define('IMAGE_DIR', 'uploads'); /* End of file constants.php */ /* Location: ./application/config/constants.php */ \ No newline at end of file diff --git a/assets/template/fcs/css/mobile.css b/assets/template/fcs/css/mobile.css new file mode 100644 index 0000000..184a738 --- /dev/null +++ b/assets/template/fcs/css/mobile.css @@ -0,0 +1,84 @@ +.detail_label{ + display: inline-block; + min-width: 160px; + width: 200px; + + padding: 0.4em 0; + font-weight: bold; + float: left; +} + +.detail{ + float: left; +} + +.footer { + color: #333333; + font-family: Arial,Verdana,Geneva,sans-serif; + font-size: 9pt; + margin: 0 auto; + text-align: center; +} + +.alert-danger .ui-bar{ + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.alert-danger .ui-body{ + background-color: #FFF8F8; +} + +.alert-warning .ui-bar{ + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; +} + +.alert-warning .ui-body{ + background-color: #FFFFFD; +} + +.alert-success .ui-bar{ + background-color: #dff0d8; + border-color: #d6e9c6; + color: #3c763d; +} + +.alert-success .ui-body{ + background-color: #F9FFF2; +} + +.custom-corners .ui-bar { + border-top-left-radius: 5px; + border-top-right-radius: 7px; +} + +.select_label{ + line-height: 3em; +} + +.text_area_height{ + height: auto !important; +} + + +@media only screen and (max-width: 500px) { + .detail{ + display: block; + float: none; + padding-bottom: 0.6em; + } + + .detail_label{ + display: block; + float: none; + width: auto; + min-width: 0; + } + + .select_label{ + line-height: normal; +} +} \ No newline at end of file diff --git a/assets/template/fcs_2019/css/mobile.css b/assets/template/fcs_2019/css/mobile.css new file mode 100644 index 0000000..184a738 --- /dev/null +++ b/assets/template/fcs_2019/css/mobile.css @@ -0,0 +1,84 @@ +.detail_label{ + display: inline-block; + min-width: 160px; + width: 200px; + + padding: 0.4em 0; + font-weight: bold; + float: left; +} + +.detail{ + float: left; +} + +.footer { + color: #333333; + font-family: Arial,Verdana,Geneva,sans-serif; + font-size: 9pt; + margin: 0 auto; + text-align: center; +} + +.alert-danger .ui-bar{ + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.alert-danger .ui-body{ + background-color: #FFF8F8; +} + +.alert-warning .ui-bar{ + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; +} + +.alert-warning .ui-body{ + background-color: #FFFFFD; +} + +.alert-success .ui-bar{ + background-color: #dff0d8; + border-color: #d6e9c6; + color: #3c763d; +} + +.alert-success .ui-body{ + background-color: #F9FFF2; +} + +.custom-corners .ui-bar { + border-top-left-radius: 5px; + border-top-right-radius: 7px; +} + +.select_label{ + line-height: 3em; +} + +.text_area_height{ + height: auto !important; +} + + +@media only screen and (max-width: 500px) { + .detail{ + display: block; + float: none; + padding-bottom: 0.6em; + } + + .detail_label{ + display: block; + float: none; + width: auto; + min-width: 0; + } + + .select_label{ + line-height: normal; +} +} \ No newline at end of file diff --git a/assets/template/rula/css/mobile.css b/assets/template/rula/css/mobile.css new file mode 100644 index 0000000..184a738 --- /dev/null +++ b/assets/template/rula/css/mobile.css @@ -0,0 +1,84 @@ +.detail_label{ + display: inline-block; + min-width: 160px; + width: 200px; + + padding: 0.4em 0; + font-weight: bold; + float: left; +} + +.detail{ + float: left; +} + +.footer { + color: #333333; + font-family: Arial,Verdana,Geneva,sans-serif; + font-size: 9pt; + margin: 0 auto; + text-align: center; +} + +.alert-danger .ui-bar{ + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.alert-danger .ui-body{ + background-color: #FFF8F8; +} + +.alert-warning .ui-bar{ + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; +} + +.alert-warning .ui-body{ + background-color: #FFFFFD; +} + +.alert-success .ui-bar{ + background-color: #dff0d8; + border-color: #d6e9c6; + color: #3c763d; +} + +.alert-success .ui-body{ + background-color: #F9FFF2; +} + +.custom-corners .ui-bar { + border-top-left-radius: 5px; + border-top-right-radius: 7px; +} + +.select_label{ + line-height: 3em; +} + +.text_area_height{ + height: auto !important; +} + + +@media only screen and (max-width: 500px) { + .detail{ + display: block; + float: none; + padding-bottom: 0.6em; + } + + .detail_label{ + display: block; + float: none; + width: auto; + min-width: 0; + } + + .select_label{ + line-height: normal; +} +} \ No newline at end of file diff --git a/assets/template/rula2018/css/mobile.css b/assets/template/rula2018/css/mobile.css new file mode 100644 index 0000000..184a738 --- /dev/null +++ b/assets/template/rula2018/css/mobile.css @@ -0,0 +1,84 @@ +.detail_label{ + display: inline-block; + min-width: 160px; + width: 200px; + + padding: 0.4em 0; + font-weight: bold; + float: left; +} + +.detail{ + float: left; +} + +.footer { + color: #333333; + font-family: Arial,Verdana,Geneva,sans-serif; + font-size: 9pt; + margin: 0 auto; + text-align: center; +} + +.alert-danger .ui-bar{ + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.alert-danger .ui-body{ + background-color: #FFF8F8; +} + +.alert-warning .ui-bar{ + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; +} + +.alert-warning .ui-body{ + background-color: #FFFFFD; +} + +.alert-success .ui-bar{ + background-color: #dff0d8; + border-color: #d6e9c6; + color: #3c763d; +} + +.alert-success .ui-body{ + background-color: #F9FFF2; +} + +.custom-corners .ui-bar { + border-top-left-radius: 5px; + border-top-right-radius: 7px; +} + +.select_label{ + line-height: 3em; +} + +.text_area_height{ + height: auto !important; +} + + +@media only screen and (max-width: 500px) { + .detail{ + display: block; + float: none; + padding-bottom: 0.6em; + } + + .detail_label{ + display: block; + float: none; + width: auto; + min-width: 0; + } + + .select_label{ + line-height: normal; +} +} \ No newline at end of file diff --git a/assets/template/rula_covid/css/booking_form.css b/assets/template/rula_covid/css/booking_form.css new file mode 100644 index 0000000..b96e9aa --- /dev/null +++ b/assets/template/rula_covid/css/booking_form.css @@ -0,0 +1,70 @@ +.form_left{ + + color: #BB1111; + width: 200px; + display: inline-block; + font-weight: bold; + height: 30px; +} + +.form_right{ + + color: #000000; + width: 200px; + height: 30px; + display: inline-block; +} + +.form_label{ + margin-top: 2em; + color: #BB1111; + width: 200px; + display: inline-block; + font-weight: bold; + +} + +#page_title{ + color: #777777; +} + +.form_buttons_container{ + float: right; + margin-top: 1em; +} + +.form_buttons_container input[type="button"], .form_buttons_container input[type="submit"] { + border-radius: 0px; + padding: 0.4em 1em; + margin: 0.3em; + color: #FFFFFF; +} + +#submit_button, #delete_button{ + background-color: #BB1111; + border: 1px solid #881111; +} + +#cancel_button{ + background-color: #000000; + border: 1px solid #000000; +} + +#date_left{ + font-size: 70px; + display: block; + margin: 40px auto; + text-align: center; +} + +#month_left{ + font-size: 25px; + clear:both; + display: block; + margin: 0 auto; + text-align: center; +} + +input[value="Checkout"]{ + display: none; +} \ No newline at end of file diff --git a/assets/template/rula_covid/css/booking_main.css b/assets/template/rula_covid/css/booking_main.css new file mode 100644 index 0000000..2bf7ed9 --- /dev/null +++ b/assets/template/rula_covid/css/booking_main.css @@ -0,0 +1,432 @@ +#filter_container{ + display: none !important; +} +#filter_link_title{ + display: none !important; +} + +* { + margin: 0; + padding: 0; +} + +a, a:visited, a:hover, a:active { + color: inherit; +} + + + +.alert{ + margin-top: 1.5em; +} + +.center{ + text-align: center; + margin: 0 auto; +} + +.calendar_container{ + + margin-top: 3em; + margin-bottom:0.4em; + float: right; +} + +.table_calendar{ + width: 450px; + height: 300px; + background-color: #f2f2f2; + + margin: 0 auto; + border-collapse: collapse; +} + +.table_calendar a.month_arrow { + font-size: 25px; + background-color: #f5945d; + display: block; + height: 70px; + line-height: 70px; + width: 55px; + padding: 0!important; + margin: 0!important; + +} + +.table_calendar a{ + text-decoration: none; + color: #000075; + font-size: 14px; + width: 30px; + height: 30px; + display: inline-block; +} + +.table_calendar .month_year a{ + color: #FFFFFF; +} + +.table_calendar td,.table_calendar th{ + text-align: center; + width: 90px; + height: 40px; + border-collapse: collapse; + font-family: 'Open Sans', sans-serif; + font-size:17px; + +} + +.month_arrow_container{ + background-color: #f27935; +} + + +.table_calendar .month_year{ + text-align: center; + background-color: #f27935; + color: #FFFFFF; + padding: 0; + margin: 0; + height: 70px; + line-height: 70px; + position: relative; + /*top: -2px;*/ + font-family: 'Open Sans', sans-serif; + font-size:30px; + font-weight: normal; +} + +.table_calendar .calendar_today a{ + + + text-align: center; + -moz-border-radius: 0.5em; /* or 50% */ + border-radius: 0.5em; /* or 50% */ + width: 40px; + height: 40px; + line-height: 40px; + display: inline-block; + background-color: #a3eaf9; + +} + +.table_calendar .selected_date a{ + + text-align: center; + -moz-border-radius: 0.5em; /* or 50% */ + border-radius: 0.5em; /* or 50% */ + width: 40px; + height: 40px; + line-height: 40px; + display: inline-block; + background-color: #fbd4be; +} + +.table_calendar .past_date a{ + color: #bababa; +} + +#booking_container_date{ + font-weight: bold; +} + +.booking_container{ + position: relative; +} + +.role_title{ + background-color: #191d23; + + height: 40px; + color: #ffffff; + line-height: 40px; + padding-left: 3em; + font-size: 17px; + font-family: 'Open Sans', sans-serif; + position: relative; + top: 1px; + margin-top: 2em; +} + +.role_title_collapse a{ + float: right; + display: inline-block; + width: 40px; + background-color: #323a45; + text-align: center; + font-size: 23px; + text-decoration: none; + color: #ffffff; +} + + +.table-wrapper { + overflow-x:scroll; + overflow-y:visible; + + margin-left: 179px; + +} + +tr.room_row > td{ + max-width: 70px; + min-width: 70px; + padding: 0; + border: 1px solid #000000; +} + + + +.table-wrapper th { + padding: 0; + width: 180px; + max-width: 180px; + min-width: 180px; + color: #ffffff; + background-color: #35383b; + text-align:center; + white-space: nowrap; + + + +} + +.table_cell_height{ + height: 25px; + line-height: 25px; + overflow: hidden; + text-overflow: ellipsis; + font-size: 1em; + /* width: 9em; */ + margin: 0 2em; + +} + +.room_name .table_cell_height{ + margin: 0; + width: 12em; +} + + +.booking_table td{ + max-height: 30px; + height: 30px; + min-height: 30px; + border: none; + overflow: hidden; +} + +.booking_table th{ + max-height: 30px; + height: 30px; + min-height: 30px; + border: 1px solid #000000; + + +} + +.table-wrapper th:first-child { + position: absolute; + left: 0; + + +} + +.table-wrapper .room_name{ + color: #FFFFFF; + background-color: #f27935; + text-align: left; + padding-left: 1em; +} + +.table-wrapper .room_free{ + background-color: #a1e3ef; + + min-width: 90px; + border: 1px solid #000000; + + /* text-align: center; */ +} + +.table-wrapper .not_avail{ + background-color: #e3e3e3; + + min-width: 90px; + border: 1px solid #000000; + + text-align: center +} + +.booking_cell a{ + display: block; + height: 100%; + width: 100%; +} + +.booking_cell{ + font-size: 13px; +} + +.room_free{ + +} + +.booked_cell{ + background-color: #e6e6e6; + color: #999999; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.my_booked_cell{ + background-color: #1fbba6; + color: #333333; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.closed{ + background-color: #2b3035; + color: #ffffff; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.room_resources{ + display: none; +} + + + +#top_left{ + float: left; + margin-top: 2em; +} + +#top_content{ + width: 780px; + margin: 0 auto; +} + +#app_links{ + margin-bottom: 2em; +} + +#app_links li{ + list-style: none; + margin-bottom: 2px; + } + +#app_links li a{ + text-decoration: none; + height: 40px; + line-height: 40px; + background-color: #F2F2F2; + color: #5E6D82; + text-align: center; + width: 280px; + display: inline-block; + + font-weight: bold; + border-radius:0.4em; +} + +#app_links li a:hover{ +color: #FFFFFF; + background-color: #323A45; +} + + +#app_links .selected{ + color: #FFFFFF; + background-color: #323A45; +} + +#filter_link_title{ + width: 280px; + text-align: center; + text-decoration: none; + background-color: #f27935; + display: inline-block; + height: 40px; + line-height: 40px; + color: #FFFFFF; + font-weight: bold; + +} + +#filter_link { + float: right; + color: #FFFFFF; + background-color: #f5945d; + border-top-right-radius: 0.2em; + border-bottom-right-radius: 0.2em; + text-decoration: none; + width: 40px; + font-size: 1.5em; + text-align: center; +} + +#filter_container { + /*display: none;*/ + color: #000000; + background-color: #ededed; + + padding-top: 3px; + padding-bottom: 3px; + text-align: left; + line-height: normal; + height: 150px; + overflow-x: scroll; + white-space: nowrap; + +} + +.filter_row{ + padding-left: 20px; + display: inline-block; + vertical-align: bottom; + height: 110px; + margin-top: 10px; +} + +.filter_row label input { + display: none;/* <-- hide the default checkbox */ +} +.filter_row label span {/* <-- style the artificial checkbox */ + height: 12px; + width: 12px; + border: 1px solid grey; + display: inline-block; + margin-right: 4px; + border-radius: 50%; +} +.filter_row [type=checkbox]:checked + span {/* <-- style its checked state */ + background-color: #f27935; +} + +.filter_row label{ + font-weight: normal; +} + +#privacy{ + margin-top: 2em; + margin-bottom: 2em; +} + + + +.jspTrack { + background-color: #f27935 !important; +} /* or just remove the background property*/ + +.jspDrag { + background-color: #fbd4be !important; + border-radius: 5px; +} \ No newline at end of file diff --git a/assets/template/rula_covid/css/bootstrap.css b/assets/template/rula_covid/css/bootstrap.css new file mode 100644 index 0000000..ed3905e --- /dev/null +++ b/assets/template/rula_covid/css/bootstrap.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/assets/template/rula_covid/css/font-awesome.css b/assets/template/rula_covid/css/font-awesome.css new file mode 100644 index 0000000..540440c --- /dev/null +++ b/assets/template/rula_covid/css/font-awesome.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} diff --git a/assets/template/rula_covid/css/landing_page.css b/assets/template/rula_covid/css/landing_page.css new file mode 100644 index 0000000..af1953e --- /dev/null +++ b/assets/template/rula_covid/css/landing_page.css @@ -0,0 +1,134 @@ +#container{ + margin: 0 auto; + padding-top: 2em; + width: 850px; + +} + +#left_container{ + float: left; + width: 400px; + margin-right: 3em; + display:inline; + height: 600px; + +} + +#right_container{ + float: right; + width: 400px; + height: 600px; + display:inline; + text-align: center; + +} + +#book_room a{ + margin-bottom: 0 !important; + +} + +#calendar_icon a{ + border-radius: 50%; + background-color: #f27935; + width: 190px; + height: 190px; + line-height: 190px; + margin: 0 auto; + box-shadow: 6px 6px 5px #888888; +} + +#calendar_icon{ + padding: 2em 0em; + margin-bottom: 1em; + background-color: #e9e9e9; +} + +#usage_title { + display: block; + color: #ffffff; + text-align: center; + margin-bottom: 0; + min-height: 3em; + line-height: 3em; + font-size: 20px; +} + +#usage_graphic{ + display: block; + color: #ffffff; + background-color: #e9e9e9; + + margin-bottom: 0; + min-height: 3em; + line-height: 3em; + font-size: 20px; + height: 485px; + padding-top: 2em; + + +} + +#text_wrapper{ + display: block; + height: 200px; + text-align: center; + background-image: url(../../../img/Book-Room-Icon1.png) ; + background-size: 200px 200px; + background-repeat: no-repeat; + background-position:center center; + padding: 25px; +} + +#used_hours{ + font-size: 78px; + line-height: 25px; + display:block; + clear:both; + +} + +#remaining{ + color: #323a45; + font-size: 22px; + line-height: 28px; + margin-top: 1em; + font-weight: normal; +} + +.remaining_number{ + color: #f27935; + + font-weight: bold; +} + +.nav_link a{ + text-decoration: none; + display: block; + color: #ffffff; + text-align: center; + margin-bottom: 20px; + min-height: 3em; + line-height: 3em; + font-size: 20px; +} + +.orange{ + background-color: #f27935; +} + +.grey{ + background-color: #ededed; +} + +.aqua{ + background-color: #1fbba6; +} + +.dark{ + background-color: #323a45; +} + +.blue{ + background-color: #14b9d6; +} \ No newline at end of file diff --git a/assets/template/rula_covid/css/mobile.css b/assets/template/rula_covid/css/mobile.css new file mode 100644 index 0000000..3306d30 --- /dev/null +++ b/assets/template/rula_covid/css/mobile.css @@ -0,0 +1,88 @@ +.detail_label{ + display: inline-block; + min-width: 160px; + width: 200px; + + padding: 0.4em 0; + font-weight: bold; + float: left; +} + +.detail{ + float: left; +} + +.footer { + color: #333333; + font-family: Arial,Verdana,Geneva,sans-serif; + font-size: 9pt; + margin: 0 auto; + text-align: center; +} + +.alert-danger .ui-bar{ + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} + +.alert-danger .ui-body{ + background-color: #FFF8F8; +} + +.alert-warning .ui-bar{ + background-color: #fcf8e3; + border-color: #faebcc; + color: #8a6d3b; +} + +.alert-warning .ui-body{ + background-color: #FFFFFD; +} + +.alert-success .ui-bar{ + background-color: #dff0d8; + border-color: #d6e9c6; + color: #3c763d; +} + +.alert-success .ui-body{ + background-color: #F9FFF2; +} + +.custom-corners .ui-bar { + border-top-left-radius: 5px; + border-top-right-radius: 7px; +} + +.select_label{ + line-height: 3em; +} + +.text_area_height{ + height: auto !important; +} + +#view_room_avail{ + display: none; +} + +@media only screen and (max-width: 500px) { + .detail{ + display: block; + float: none; + padding-bottom: 0.6em; + } + + .detail_label{ + display: block; + float: none; + width: auto; + min-width: 0; + } + + .select_label{ + line-height: normal; +} +} + diff --git a/assets/template/rula_covid/css/sitelibrary.css b/assets/template/rula_covid/css/sitelibrary.css new file mode 100644 index 0000000..4c66cc8 --- /dev/null +++ b/assets/template/rula_covid/css/sitelibrary.css @@ -0,0 +1,156 @@ +/* RyersonU Library Sitewide CSS +Last Modified by: CN on Mar 25, 2013 */ + +/*** Content ***/ +/* Global Overrides */ +.entry-content iframe { + margin: 0; + border: none; + overflow: hidden; +} + +.inc-height { + line-height: 1.5em; +} + +/* images without borders */ +img[class*="wp-image-"].noborder, #content .gallery .gallery-icon { + border: 0; +} + +/* Override for book banner */ + +#titles-wrap { + opacity: 0.9; +} + +/* Form fields styling */ +.req { + font-size: 125%; + color: red; +} + +.alignfields label, +.alignfields input { + display: inline-block; +} +.alignfields label { + width: 200px; + vertical-align: top; +} +.alignfields input[type="text"] { + margin: 2px 0; +} +.alignfields .column { + padding: 0; +} + +/* For auto generated 2 column layouts */ +.auto-two-columns { + -moz-column-count: 2; + -webkit-column-count: 2; + column-count: 2; +} + +/* For 2 Column layouts */ +.column { + float: left; + padding: 0 1%; +} +.full { + width: 97.5%; +} +.half { + width: 47.5%; +} +.wide { + width: 64%; +} +.narrow { + width: 31%; +} +.clearfix { + overflow: auto; +} +/* column left border */ +.leftborder { + border-left: 1px dotted #CCC; +} +.leftpadding { + padding: 0 0 0 2%; +} +/* For Announcements */ +#announce { + background: #FEFBF4; + border: 2px solid #D50202; + padding: 0.5em; +} + +/* Used in Collection Development Policy */ +.upper-roman { + list-style-type: upper-roman; +} + +/* For Database Description pages */ +#concurrents { + border: 2px #F37626 solid; + width: 50%; + padding: 0.5em; +} + +/* For Database by Subjects Page */ +.entry-content iframe[src^="http://ryerson.scholarsportal.info"] { + height: 40px; + margin-bottom: -15px; +} + +/* New Books */ +#newbooksb { + max-width: 100%; + max-height: 175px; + overflow: hidden; +} +#newbooksb table { + margin: 0 auto; +} + +/* Blog */ +#secondary #linkcat-189,#secondary #polls-widget-2 { + margin-bottom:.4em; + padding-bottom:1.2em; + border-bottom:1px solid #D9D9D9; +} +#secondary hr { + background-color:#D9D9D9; + color:#D9D9D9; +} +#secondary #polls-widget-2 { + border-top:1px solid #D9D9D9; +} + +/* Global Navigation Increase Padding for this site only */ +#top-nav-list a, +#top-nav-list a:link, +#top-nav-list a:visited { + padding: 7px 35px; +} + + +@media (max-width: 40.5em) { /* approx 650px, force single column */ + .auto-two-columns { + -moz-column-count: 1; + -webkit-column-count: 1; + column-count: 1; + } + .column { + padding: 0 2% !important; + } + .half, + .wide, + .narrow { + width: 95%; + } + .leftborder { + border-left: none; + } +} diff --git a/assets/template/rula_covid/css/style.css b/assets/template/rula_covid/css/style.css new file mode 100644 index 0000000..82141eb --- /dev/null +++ b/assets/template/rula_covid/css/style.css @@ -0,0 +1,2514 @@ +/* +Theme Name: Ryerson University Library & Archives +Theme URI: https://github.com/ryersonlibrary/rula_wordpress +Author: Ryerson University Library & Archives +Author URI: https://github.com/ryersonlibrary +Description: Theme developed for the Ryerson University Library & Archives (RULA) website, also for use on other RULA sites. +Version: 1.23.0 +GitHub Theme URI: https://github.com/ryersonlibrary/rula_wordpress +GitHub Branch: master +*/ + +/** WARNING + * This theme is intended to be used in conjunction with the GitHub Updater + * plugin (https://github.com/afragen/github-updater). Changes made to files + * in this theme directly in the WordPress theme editor will be overwritten + * during automatic updates. + */ + +/* Invalid CSS that works on everything except Opera */ +/* Forces vertical scrollbar */ +html { + overflow-y: scroll; +} + +/* Typography */ +.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { + font-weight: 700; +} + +p:last-child { + margin-bottom: 0; +} + +ul:last-child { + margin-bottom: 0; +} + +h1 { + font-size: 1.5em; +} +h2 { + font-size: 1.35em; +} +h3 { + font-size: 1.2em; +} +h4 { + font-size: 1.1em; +} +h5 { + font-size: 1em; +} +h6 { + font-size: 1em; +} + +.btn { + border-radius: 0; +} + +a.btn:visited { + color: #FFF; +} + +.btn-primary, a.btn-primary { + color: #FFF; + background-color: #002d72; + border-color: #002d72; +} + +.btn-primary:hover, a.btn-primary:hover { + background-color: #004C9B; + color: #FFEE00; +} + +/* =Extra Normalize Rules +----------------------------------------------- */ +/* Force Safari to style search form fields */ +input[type="search"] { + -webkit-appearance: textfield; +} + +/* =Fonts +----------------------------------------------- */ +/* subsite name */ +@font-face { + font-family: 'Belleza'; + src: url('//themes.googleusercontent.com/static/fonts/belleza/v1/6vro92rxXIoeIhMkLHqPOg.eot'); + src: local('☺'), + url('//themes.googleusercontent.com/static/fonts/belleza/v1/kccRZmnnm8BZQBlMJUI5mwLUuEpTyoUstqEm5AMlJo4.woff') format('woff'); + font-style: normal; + font-weight: 400; +} + + +/* =Structure +----------------------------------------------- */ + +body { + margin: 0px; + position: relative; + line-height: 1.6em; + background-color: #696969; + font-family: Verdana, Geneva, sans-serif; +} + +/* Alignment */ +.alignleft { + display: inline; + float: left; + margin-right: 1.625em; +} +.alignright { + display: inline; + float: right; + margin-left: 1.625em; +} +.aligncenter { + clear: both; + display: block; + margin-left: auto; + margin-right: auto; +} + +/* Right Content */ +.left-sidebar #primary { + float: right; + border-right: none; + border-left: 1px solid #E8E8E8; +} +.left-sidebar #secondary { + float: left; + margin: 0 0 0 1.5%; +} + +/* One column */ +.singular #primary, .one-column #primary { +} +.one-column #content { + margin: 0 7.6%; + width: 84%; +} +.one-column #nav-below { + border-bottom: 1px solid #ddd; + margin-bottom: 1.625em; +} +.one-column #secondary { + float: none; + margin: 0 7.6%; + width: auto; +} + +/* No Sidebar Template - Singular */ +.singular #content, +.left-sidebar.singular #content { +} + +/* One column */ +.singular #primary, +.one-column #primary, +.page-template-page-ask #primary { +} + +/* Attachments */ +.singular .image-attachment .entry-content { + margin: 0 auto; + width: auto; +} +.singular .image-attachment .entry-description { + margin: 0 auto; + width: 68.9%; +} + +/* Simplify the pullquotes and pull styles */ +.one-column.singular blockquote.pull { + margin: 0 0 1.625em; +} +.one-column.singular .pull.alignleft { + margin: 0 1.625em 0 0; +} +.one-column.singular .pull.alignright { + margin: 0 0 0 1.625em; +} +.one-column.singular .entry-meta .edit-link a { + position: absolute; + left: 0; +/* top: 40px; */ +} +.one-column.singular #author-info { + margin: 2.2em -8.8% 0; + padding: 20px 8.8%; +} +/* Make sure we have room for our comment avatars */ +.one-column .commentlist > li.comment { + margin-left: 102px; + width: auto; +} +/* Talking avatars take up too much room at this size */ +.one-column .commentlist > li.comment { + margin-left: 0; +} +.one-column .commentlist > li.comment .comment-meta, +.one-column .commentlist > li.comment .comment-content { + margin-right: 85px; +} +.one-column .commentlist .avatar { + background: transparent; + display: block; + padding: 0; + top: 1.625em; + left: auto; + right: 1.625em; +} +.one-column .commentlist .children .avatar { + background: none; + padding: 0; + position: absolute; + top: 2.2em; + left: 2.2em; +} +.one-column #respond { + width: auto; +} + + +a { + color: #0077C8; + text-decoration: none; +} +a:visited { + color: #0077C8; +} +a:hover { + color: #002D72; + text-decoration: underline; +} + +/* Forms */ +button, +input[type="button"], +input[type="reset"], +input[type="submit"] { + border: 1px solid #b5b5b5; + background: #c5c5c5; + color: rgba(0, 0, 0, .8); + padding: 6px 12px; +} + +button:hover, +input[type="button"]:hover, +input[type="reset"]:hover, +input[type="submit"]:hover { + border-color: #ccc; + background: #ccc +} + +button:focus, +input[type="button"]:focus, +input[type="reset"]:focus, +input[type="submit"]:focus, +button:active, +input[type="button"]:active, +input[type="reset"]:active, +input[type="submit"]:active { + border-color: #aaa; + background: #aaa; +} + +.navbar-form > input[type="text"], +.navbar-form > input[type="email"], +.navbar-form > input[type="url"], +.navbar-form > input[type="password"], +.navbar-form > input[type="search"], +.navbar-form > input[type="number"], +.navbar-form > input[type="tel"], +.navbar-form > input[type="range"], +.navbar-form > input[type="date"], +.navbar-form > input[type="month"], +.navbar-form > input[type="week"], +.navbar-form > input[type="time"], +.navbar-form > input[type="datetime"], +.navbar-form > input[type="datetime-local"], +.navbar-form > input[type="color"], +.navbar-form > textarea { + padding: 6px 12px; + background-color: #e5e5e5; + border: 1px solid #e5e5e5; + color: #666; +} + +input[type="text"]:focus, +input[type="email"]:focus, +input[type="url"]:focus, +input[type="password"]:focus, +input[type="search"]:focus, +input[type="number"]:focus, +input[type="tel"]:focus, +input[type="range"]:focus, +input[type="date"]:focus, +input[type="month"]:focus, +input[type="week"]:focus, +input[type="time"]:focus, +input[type="datetime"]:focus, +input[type="datetime-local"]:focus, +input[type="color"]:focus, +textarea:focus { + color: #111; +} + +textarea { + width: 100%; +} + +.search_form { + position: relative; +} + +.search_form .search_input, +.search_form input[type="search"] { + display: inline-block; + width: 100%; +} + +.search_form button, +.search_form input[type="submit"] { + position: absolute; + top: 0; + right: 0; +} + +.select_form { + position: relative; +} + +.select_form select { + display: block; + background: #e5e5e5; + width: 100%; + padding: 7px 8px; + font-size: inherit; + -webkit-appearance: none; +} + +.select_form input[type="submit"], +.select_form button[type="submit"] { + position: absolute; + top: 0; + right: 0; +} + +/* Accessibility Links */ +.screen-reader-text, .assistive-text { + position: absolute !important; + clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ + clip: rect(1px, 1px, 1px, 1px);; +} + +a.assistive-text:active, +a.assistive-text:focus { + background: #eee; + border-bottom: 1px solid #ddd; + color: #1982d1; + clip: auto; + font-size: 14px; + position: absolute; + text-decoration: underline; + top: 5px; + left: 5px; + z-index: 10000; +} + +/* =Header +-------------------------------------------------------------- */ +#main-navigation .current-menu-item a { + color: #FFEE00; +} + +/* =Content +----------------------------------------------- */ + +#main { + /*padding-top: 1em;*/ + padding-bottom: 1em; +} + +.breadcrumbs { + color: #888; + font-size: 0.9em; + font-weight: 700; + margin-bottom: 1.5em; +} + +.breadcrumbs a { + font-weight: normal; +} + +.page-title { + color: #666; + font-size: 10px; + font-weight: 500; + letter-spacing: 0.1em; + line-height: 2.6em; + margin: 0 0 2.6em; + text-transform: uppercase; +} +.page-title a { + font-size: 12px; + font-weight: bold; + letter-spacing: 0; + text-transform: none; +} +.hentry, +.no-results { + border-bottom: 1px solid #ddd; + margin: 0 0 1.625em; + padding: 0 0 1.625em; + position: relative; +} +.hentry:last-child, +.no-results { + border-bottom: none; +} +.blog .sticky .entry-header .entry-meta { + clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ + clip: rect(1px, 1px, 1px, 1px); + position: absolute !important; +} +.entry-title, +.entry-header .entry-meta { + padding-right: 76px; +} +.entry-title { + font-size: 2.5em; + margin-top: 0; + line-height: 1.5em; +} +.entry-title a { + text-decoration: none; +} +.entry-title a:hover, +.entry-title a:focus, +.entry-title a:active { + color: #1982d1; +} +.entry-meta { + color: #666; + clear: both; + font-size: 12px; + line-height: 18px; +} +.entry-meta a { + font-weight: bold; +} +.single-author .entry-meta .by-author { + display: none; +} +.comment-content h1, +.comment-content h2 { + color: #000; + font-weight: bold; + margin: 0 0 .8125em; +} +.comment-content h3 { + font-size: 10px; + letter-spacing: 0.1em; + line-height: 2.6em; + text-transform: uppercase; +} + +/* +.entry-content table, +.comment-content table { + border-bottom: 1px solid #ddd; + margin: 0 0 1.625em; + max-width: 100%; +} +.entry-content th, +.comment-content th { + color: #666; + font-weight: 500; + letter-spacing: 0.1em; + text-transform: uppercase; +} +.entry-content td, +.comment-content td { + border-top: 1px solid #ddd; + border-left: 2px solid #fff; + padding: 6px; +} +*/ +.entry-content #s { + width: 75%; +} +.comment-content ul, +.comment-content ol { + margin-bottom: 1.625em; +} +.comment-content ul ul, +.comment-content ol ol, +.comment-content ul ol, +.comment-content ol ul { + margin-bottom: 0; +} +dl.gallery-item { + margin: 0; +} +.page-link { + clear: both; + display: block; + margin: 0 0 1.625em; +} +.page-link a { + background: #eee; + color: #373737; + margin: 0; + padding: 2px 3px; + text-decoration: none; +} +.page-link a:hover { + background: #888; + color: #fff; + font-weight: bold; +} +.page-link span { + margin-right: 6px; +} +.entry-meta .edit-link a, +.commentlist .edit-link a { + float: right; +} +.entry-meta .edit-link a:hover, +.commentlist .edit-link a:hover { +} +.entry-content .edit-link { + clear: both; + display: block; +} + +/* Images */ +img { + max-width: 100%; +} + +.entry-content img, +.comment-content img, +.widget img, +img { + /* max-width: 97.5%; Fluid images for posts, comments, and widgets - commented out because of blurriness (Jul15) */ +} +img[class*="align"], +img[class*="wp-image-"], +img[class*="attachment-"] { + height: auto; /* Make sure images with WordPress-added height and width attributes are scaled correctly */ +} +img.size-full, +img.size-large { + max-width: 97.5%; + width: auto; /* Prevent stretching of full-size and large-size images with height and width attributes in IE8 */ + height: auto; /* Make sure images with WordPress-added height and width attributes are scaled correctly */ +} +.entry-content img.wp-smiley { + border: none; + margin-bottom: 0; + margin-top: 0; + padding: 0; +} +img.alignleft, +img.alignright, +img.aligncenter { + margin-bottom: 1.625em; +} + +.wp-caption { + margin-top: 0.4em; +} +.wp-caption { + background: #eee; + margin-bottom: 1.625em; + max-width: 96%; + padding: 9px; +} +.wp-caption img { + display: block; + margin: 0 auto; + max-width: 98%; +} +.wp-caption .wp-caption-text, +.gallery-caption { + color: #666; + font-family: Georgia, serif; + font-size: 12px; +} +.wp-caption .wp-caption-text { + margin: 0; + padding: 0 0 0 1em; + position: relative; +} +#content .gallery { + margin: 0 auto 1.625em; +} +#content .gallery a img { + border: none; +} +img#wpstats { + display: block; + margin: 0 auto 1.625em; +} +#content .gallery-columns-4 .gallery-item { + width: 23%; + padding-right: 2%; +} +#content .gallery-columns-4 .gallery-item img { + width: 100%; + height: auto; +} + +/* Image borders */ +img[class*="align"], +img[class*="wp-image-"], +#content .gallery .gallery-icon img { +} +.wp-caption img { + border-color: #eee; +} +a:focus img[class*="align"], +a:hover img[class*="align"], +a:active img[class*="align"], +a:focus img[class*="wp-image-"], +a:hover img[class*="wp-image-"], +a:active img[class*="wp-image-"], +#content .gallery .gallery-icon a:focus img, +#content .gallery .gallery-icon a:hover img, +#content .gallery .gallery-icon a:active img { + border-color: #bbb; +} +.wp-caption a:focus img, +.wp-caption a:active img, +.wp-caption a:hover img {/* ... including captioned images! */ + background: #fff; + border-color: #ddd; +} + +/* Make sure embeds and iframes fit their containers */ +embed, +iframe, +object { + max-width: 100%; +} + +/* Password Protected Posts */ +.post-password-required input[type=password] { + margin: 0.8125em 0; +} +.post-password-required input[type=password]:focus { + background: #f7f7f7; +} + +/* Author Info */ +#author-info { + font-size: 12px; + overflow: hidden; +} +.singular #author-info { + background: #f9f9f9; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 2.2em -35.6% 0 -35.4%; + padding: 20px 35.4%; +} +.archive #author-info { + border-bottom: 1px solid #ddd; + margin: 0 0 2.2em; + padding: 0 0 2.2em; +} +#author-avatar { + float: left; + margin-right: -78px; +} +#author-avatar img { + background: #fff; + -moz-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: 0 1px 2px #bbb; + -moz-box-shadow: 0 1px 2px #bbb; + box-shadow: 0 1px 2px #bbb; + padding: 3px; +} +#author-description { + float: left; + margin-left: 108px; +} +#author-description h2 { + color: #000; + font-size: 15px; + font-weight: bold; + margin: 5px 0 10px; +} + +/* +Post Formats Headings +To hide the headings, display: none the ".entry-header .entry-format" selector, +and remove the padding rules below. +*/ +.entry-header .entry-format { + display: none; +} +.entry-header hgroup .entry-title { + padding-top: 15px; +} +article.format-aside .entry-content, +article.format-link .entry-content, +article.format-status .entry-content { + padding: 20px 0 0; +} +article.format-status .entry-content { + min-height: 65px; +} +.recent-posts .entry-header .entry-format { + display: none; +} +.recent-posts .entry-header hgroup .entry-title { + padding-top: 0; +} + +/* Singular content styles for Posts and Pages */ +blockquote.pull { + font-size: 21px; + font-weight: bold; + line-height: 1.6125em; + margin: 0 0 1.625em; + text-align: center; +} +.singular blockquote.pull { + margin: 0 -22.25% 1.625em; +} +.pull.alignleft { + margin: 0 1.625em 0 0; + text-align: right; +} +.singular .pull.alignleft { + margin: 0 1.625em 0 -22.25%; +} +.pull.alignright { + margin: 0 0 0 1.625em; + text-align: left; +} +blockquote.pull.alignleft, +blockquote.pull.alignright { + width: 33%; +} +.singular .pull.alignright { + margin: 0 -22.25% 0 1.625em; +} +.singular blockquote.pull.alignleft, +.singular blockquote.pull.alignright { + width: 33%; +} + +/* =Gallery +----------------------------------------------- */ + +.format-gallery .gallery-thumb { + float: left; + display: block; + margin: .375em 1.625em 0 0; +} + + +/* =Quote +----------------------------------------------- */ + +.format-quote blockquote { + color: #555; + font-size: 17px; +} + + +/* =Image +----------------------------------------------- */ + +.indexed.format-image .entry-header { + min-height: 61px; /* Prevent the comment icon from colliding with the image when there is no title */ +} +.indexed.format-image .entry-content { + padding-top: 0.5em; +} +.indexed.format-image .entry-content p { + margin: 1em 0; +} +.indexed.format-image .entry-content p:first-child, +.indexed.format-image .entry-content p:first-child a, +.indexed.format-image .entry-content p:first-child img { + display: block; + margin: 0; +} +.indexed.format-image .entry-content .wp-caption .wp-caption-text { + margin: 0; + padding-bottom: 1em; +} +.indexed.format-image footer.entry-meta { + background: #ddd; + padding: 4%; + overflow: hidden; + max-width: 96%; +} +.indexed.format-image div.entry-meta { + display: inline-block; + float: left; + width: 35%; +} +.indexed.format-image div.entry-meta + div.entry-meta { + float: none; + width: 65%; +} +.indexed.format-image .entry-meta span.cat-links, +.indexed.format-image .entry-meta span.tag-links, +.indexed.format-image .entry-meta span.comments-link { + display: block; +} +.indexed.format-image footer.entry-meta a { + color: #444; +} +.indexed.format-image footer.entry-meta a:hover { + color: #fff; +} +#content .indexed.format-image img { + border: none; + max-width: 100%; + padding: 0; +} +.indexed.format-image .wp-caption { + background: #111; + margin-bottom: 0; + max-width: 96%; + padding: 2% 2% 0; +} +.indexed.format-image .wp-caption .wp-caption-text { + color: #ddd; +} +.indexed.format-image .wp-caption .wp-caption-text:before { + color: #444; +} +.indexed.format-image a:hover img { + opacity: 0.8; +} + +/* =Search Results +----------------------------------------------- */ +.search-results .type-page .comments-link { + display: none; +} + +/* =error404 +----------------------------------------------- */ + +.error404 #primary { + float: none; + margin: 0; + width: 99.9%; +} +.error404 #primary #content { + margin: 0 7.6%; + width: auto; +} + +.error404 #main #searchform { + background: #f9f9f9; + border: 1px solid #ddd; + border-width: 1px 0; + margin: 1.625em -8.9%; + overflow: hidden; + padding: 1.625em 8.9%; +} + +.error404 #searchsubmit { + height: 24px; + margin-top: 5px; +} + +.error404 #main .widget { + clear: none; + float: left; + margin-right: 3.7%; + width: 30.85%; +} +.error404 #main .widget_archive { + margin-right: 0; +} +.error404 #main .widget_tag_cloud { + float: none; + margin-right: 0; + width: 100%; +} +.error404 .widgettitle { + font-size: 10px; + letter-spacing: 0.1em; + line-height: 2.6em; + text-transform: uppercase; +} + + +/* =Attachments +----------------------------------------------- */ + +.image-attachment div.attachment { + background: #f9f9f9; + border: 1px solid #ddd; + border-width: 1px 0; + overflow: hidden; + padding: 1.625em 1.625em 0; + text-align: center; +} +.image-attachment div.attachment img { + display: block; + height: auto; + margin: 0 auto 1.625em; + max-width: 100%; +} +.image-attachment div.attachment a img { + border-color: #f9f9f9; +} +.image-attachment div.attachment a:focus img, +.image-attachment div.attachment a:hover img, +.image-attachment div.attachment a:active img { + border-color: #ddd; + background: #fff; +} +.image-attachment .entry-caption p { + font-size: 10px; + letter-spacing: 0.1em; + line-height: 2.6em; + margin: 0 0 2.6em; + text-transform: uppercase; +} + + +/* =Navigation for Posts +-------------------------------------------------------------- */ + +#content nav { + clear: both; + overflow: hidden; + padding: 0 0 1.625em; +} +#content nav a { + font-size: 12px; + font-weight: bold; + line-height: 2.2em; +} +#nav-above { + padding: 0 0 1.625em; +} +#nav-above { + display: none; +} +.paged #nav-above { + display: block; +} +.nav-previous { + float: left; + width: 49.5%; +} +.nav-next { + float: right; + text-align: right; + width: 49.5%; +} +#content nav .meta-nav { + font-weight: normal; +} + +/* Singular navigation */ +#nav-single { + float: right; + position: relative; + top: -0.3em; + text-align: right; + z-index: 1; +} +#nav-single .nav-previous, +#nav-single .nav-next { + width: auto; +} +#nav-single .nav-previous { + padding-right: 5px; +} + + +/* =Widgets +----------------------------------------------- */ + +.widget { + clear: both; + margin: 0 0 1em; +} +.widget-title { + font-weight: 800; + letter-spacing: 0.1em; + margin-bottom: 0.2em; +} + +.widget ul { + margin: 0; +} + +/* Search Widget */ +.widget_search form { + margin: 0 0 1.625em; +} +.widget_search #s { + width: 77%; +} +.widget_search #searchsubmit { + background: #ddd; + border: 1px solid #ccc; + -webkit-box-shadow: inset 0px -1px 1px rgba(0, 0, 0, 0.09); + -moz-box-shadow: inset 0px -1px 1px rgba(0, 0, 0, 0.09); + box-shadow: inset 0px -1px 1px rgba(0, 0, 0, 0.09); + color: #888; + font-size: 13px; + line-height: 25px; + position: relative; + top: -2px; +} +.widget_search #searchsubmit:active { + background: #1982d1; + border-color: #0861a5; + -webkit-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.1); + -moz-box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.1); + box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.1); + color: #bfddf3; +} + +/* Twitter */ +.widget_twitter li { + list-style-type: none; + margin-bottom: 14px; +} +.widget_twitter .timesince { + display: block; + font-size: 11px; + margin-right: -10px; + text-align: right; +} + +/* Widget Image */ +.widget_image img { + border: 0; + padding: 0; + height: auto; + max-width: 100%; +} + +/* Calendar Widget */ + +.widget_calendar #wp-calendar { + color: #555; + width: 95%; + text-align: center; +} +.widget_calendar #wp-calendar caption, +.widget_calendar #wp-calendar td, +.widget_calendar #wp-calendar th { + text-align: center; +} +.widget_calendar #wp-calendar caption { + font-size: 11px; + font-weight: 500; + padding: 5px 0 3px 0; + text-transform: uppercase; +} +.widget_calendar #wp-calendar th { + background: #f4f4f4; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + font-weight: bold; +} +.widget_calendar #wp-calendar tfoot td { + background: #f4f4f4; + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} + +.widget_analytics_container { + margin: 0; +} + +/* widget_nav_menu */ +.widget_nav_menu .widget-title { + margin-top: 0; + margin-bottom: 5px; + background: #000; + color: #FFF; + padding: 1em; +} + +.widget_nav_menu ul { + padding: 0; + list-style-type: none; +} + +.widget_nav_menu li { + padding: 0em; + font-weight: 700; +} + +.widget_nav_menu li + li { + margin-top: 5px; +} + +.widget_nav_menu li a { + display: block; + padding: 0.5em 1em; + background: #FFF; + color: #000; +} + +.widget_nav_menu li a:hover { + background: #E6E6E6; +} + +.widget_nav_menu .menu-item-has-children > a { + background: #000; + color: #FFF; + padding: 1em; +} + +.widget_nav_menu .menu-item-has-children > a:hover { + color: #000; +} + +.widget_nav_menu .menu-item-object-custom.menu-item-has-children > a { + pointer-events: none; +} + +.widget_nav_menu .sub-menu li { + margin-top: 5px; +} + +/* widget_recent_entries */ +.widget_recent_entries .widget-title { + margin-top: 0; + margin-bottom: 5px; + background: #000; + color: #FFF; + padding: 1em; +} + +.widget_recent_entries ul { + padding: 0; + list-style-type: none; +} + +.widget_recent_entries li { + padding: 0em; + font-weight: 700; +} + +.widget_recent_entries li + li { + margin-top: 5px; +} + +.widget_recent_entries li a { + display: block; + padding: 0.5em 1em; + background: #FFF; + color: #000; +} + +.widget_recent_entries li a:hover { + background: #E6E6E6; +} + +/* widget_categories */ +.widget_categories .widget-title { + margin-top: 0; + margin-bottom: 5px; + background: #000; + color: #FFF; + padding: 1em; +} + +.widget_categories ul { + padding: 0; + list-style-type: none; +} + +.widget_categories li { + padding: 0em; + font-weight: 700; +} + +.widget_categories li + li { + margin-top: 5px; +} + +.widget_categories li a { + display: block; + padding: 0.5em 1em; + background: #FFF; + color: #000; +} + +.widget_categories li a:hover { + background: #E6E6E6; +} + +/* widget_archive */ +.widget_archive .widget-title { + margin-top: 0; + margin-bottom: 5px; + background: #000; + color: #FFF; + padding: 1em; +} + +.widget_archive select { + width: 100% +} + +/* .page-header */ +.archive .page-header { + margin: 0; + padding: 0; +} + +.archive .page-title { + font-size: 1.5em; + font-weight: 700; + letter-spacing: unset; + color: #000; + margin-bottom: 0; +} + +/* =Comments +----------------------------------------------- */ + +#comments-title { + color: #666; + font-size: 10px; + font-weight: 500; + line-height: 2.6em; + padding: 0 0 2.6em; + text-transform: uppercase; +} +.nopassword, +.nocomments { + color: #aaa; + font-size: 24px; + font-weight: 100; + margin: 26px 0; + text-align: center; +} +.commentlist { + list-style: none; + margin: 0 auto; + width: 68.9%; + padding: 0; +} +.content .commentlist, +.page-template-sidebar-page-php .commentlist { + width: 100%; /* reset the width for the one-column and sidebar page layout */ +} +.commentlist > li.comment { + background: #f6f6f6; + border: 1px solid #ddd; + -moz-border-radius: 3px; + border-radius: 3px; + margin: 0 0 1.625em; + padding: 1.625em; + position: relative; +} +.commentlist .pingback { + margin: 0 0 1.625em; + padding: 0 1.625em; +} +.commentlist .children { + list-style: none; + margin: 0; +} +.commentlist .children li.comment { + background: #fff; + border-left: 1px solid #ddd; + -moz-border-radius: 0 3px 3px 0; + border-radius: 0 3px 3px 0; + margin: 1.625em 0 0; + padding: 1.625em; + position: relative; +} +.commentlist .children li.comment .fn { + display: block; +} +.comment-meta .fn { + font-style: normal; +} +.comment-meta { + color: #666; + font-size: 12px; + line-height: 2.2em; +} +.commentlist .children li.comment .comment-meta { + line-height: 1.625em; + margin-left: 50px; +} +.commentlist .children li.comment .comment-content { + margin: 1.625em 0 0; +} +.comment-meta a { + font-weight: bold; +} +.comment-meta a:focus, +.comment-meta a:active, +.comment-meta a:hover { +} +.commentlist .avatar { + -moz-border-radius: 3px; + border-radius: 3px; + -webkit-box-shadow: 0 1px 2px #ccc; + -moz-box-shadow: 0 1px 2px #ccc; + box-shadow: 0 1px 2px #ccc; + left: -102px; + padding: 0; + position: absolute; + top: 0; +} +.commentlist > li:before { + content: url(images/comment-arrow.png); + left: -21px; + position: absolute; +} +.commentlist > li.pingback:before { + content: ''; +} +.commentlist .children .avatar { + background: none; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; + left: 2.2em; + padding: 0; + top: 2.2em; +} +a.comment-reply-link { + background: #eee; + -moz-border-radius: 3px; + border-radius: 3px; + color: #666; + display: inline-block; + font-size: 12px; + padding: 0 8px; + text-decoration: none; +} +a.comment-reply-link:hover, +a.comment-reply-link:focus, +a.comment-reply-link:active { + background: #888; + color: #fff; +} +a.comment-reply-link > span { + display: inline-block; + position: relative; + top: -1px; +} + +/* Post author highlighting */ +.commentlist > li.bypostauthor { + background: #ddd; + border-color: #d3d3d3; +} +.commentlist > li.bypostauthor .comment-meta { + color: #575757; +} +.commentlist > li.bypostauthor .comment-meta a:focus, +.commentlist > li.bypostauthor .comment-meta a:active, +.commentlist > li.bypostauthor .comment-meta a:hover { +} +.commentlist > li.bypostauthor:before { + content: url(images/comment-arrow-bypostauthor.png); +} + +/* Post Author threaded comments */ +.commentlist .children > li.bypostauthor { + background: #ddd; + border-color: #d3d3d3; +} + +/* sidebar-page.php comments */ +/* Make sure we have room for our comment avatars */ +.page-template-sidebar-page-php .commentlist > li.comment, +.page-template-sidebar-page-php.commentlist .pingback { + margin-left: 102px; + width: auto; +} +/* And a full-width comment form */ +.page-template-sidebar-page-php #respond { + width: auto; +} + +/* Comment Form */ +#respond { + -moz-border-radius: 3px; + background: #ddd; +} +#respond input[type="text"], +#respond textarea { + background: #fff; + -moz-border-radius: 5px; + -moz-box-shadow: inset 0 1px 3px rgba(204,204,204,0.95); +} +#respond .comment-form-author, +#respond .comment-form-email, +#respond .comment-form-url, +#respond .comment-form-comment { + position: relative; +} +#respond .comment-form-author label, +#respond .comment-form-email label, +#respond .comment-form-url label, +#respond .comment-form-comment label { + -moz-border-radius: 3px; + -moz-box-shadow: 1px 2px 2px rgba(204,204,204,0.8); + padding: 3px 5px; + background: #eee; +} +#respond input[type="text"]:focus, +#respond textarea:focus { + z-index: 1; +} +#respond textarea { +} +#respond .comment-form-author .required, +#respond .comment-form-email .required { + color: #bd3500; + font-size: 1.5em; + font-weight: bold; + left: 73%; + position: absolute; + top: 33px; + z-index: 1; +} +#respond .logged-in-as, +#respond .must-log-in, +#respond .comment-notes { +/* text-indent: 2em; */ +} +#respond .form-submit { + float: right; + margin-top: -10px; +} +#respond input#submit { + background: #222; + -moz-border-radius: 3px; + -moz-box-shadow: 0px 1px 2px rgba(0,0,0,0.3); + color: #eee; +} +#respond input#submit:active { + background: #1982d1; + color: #bfddf3; +} +#respond #cancel-comment-reply-link { + color: #666; + margin-left: 10px; + text-decoration: none; +} +#respond .logged-in-as a:hover, +#respond #cancel-comment-reply-link:hover { + text-decoration: underline; +} +.commentlist #respond { + margin: 1.625em 0 0; + width: auto; +} +#reply-title { + color: #373737; + font-size: 1.4em; + margin: 0; +} +#cancel-comment-reply-link { + color: #888; + display: block; + font-size: 10px; + font-weight: normal; + line-height: 2.2em; + letter-spacing: 0.05em; + position: absolute; + right: 1.625em; + text-decoration: none; + text-transform: uppercase; + top: 1.1em; +} +#cancel-comment-reply-link:focus, +#cancel-comment-reply-link:active, +#cancel-comment-reply-link:hover { + color: #ff4b33; +} +#respond label { + line-height: 2.2em; +} +#respond input[type=text] { + display: block; + height: 16px; + width: 75%; +} +p.comment-form-comment { +} +.form-allowed-tags { + display: none; +} + + +/* =Footer +----------------------------------------------- */ + +#colophon { + margin-top: 0.5em; + clear: both; +} +#supplementary { + border-top: 1px solid #ddd; + padding: 1.625em 7.6%; + overflow: hidden; +} + +/* Two Footer Widget Areas */ +#supplementary.two .widget-area { + float: left; + margin-right: 3.7%; + width: 48.1%; +} +#supplementary.two .widget-area + .widget-area { + margin-right: 0; +} + +/* Three Footer Widget Areas */ +#supplementary.three .widget-area { + float: left; + margin-right: 3.7%; + width: 30.85%; +} +#supplementary.three .widget-area + .widget-area + .widget-area { + margin-right: 0; +} + +#footer img { + margin: 0; + vertical-align: middle; + border: 0; +} + +/* =Responsive Structure +----------------------------------------------- */ + +@media (max-width: 53em) { /* approx 850px */ + /* Remove the margin on singular articles */ + .singular .entry-header, + .singular .entry-content, + .singular footer.entry-meta, + .singular #comments-title { + width: 100%; + } + /* Simplify the pullquotes and pull styles */ + .singular blockquote.pull { + margin: 0 0 1.625em; + } + .singular .pull.alignleft { + margin: 0 1.625em 0 0; + } + .singular .pull.alignright { + margin: 0 0 0 1.625em; + } + .singular .entry-meta .edit-link a { + left: 0; + position: absolute; + } + .singular #author-info { + margin: 2.2em -8.8% 0; + padding: 20px 8.8%; + } + /* Make sure we have room for our comment avatars */ + .commentlist { + width: 100%; + } + .commentlist > li.comment, + .commentlist .pingback { + margin-left: 102px; + width: auto; + } + /* And a full-width comment form */ + #respond { + width: auto; + } + /* No need to float footer widgets at this size */ + #colophon #supplementary .widget-area { + float: none; + margin-right: 0; + width: auto; + } + /* No need to float 404 widgets at this size */ + .error404 #main .widget { + float: none; + margin-right: 0; + width: auto; + } + +} +@media (max-width: 40.5em) { /* approx 650px */ + /* Reduce font-sizes for better readability on smaller devices */ + blockquote { + margin: 0; + } + blockquote.pull { + font-size: 17px; + } + /* Floated content doesn't work well at this size */ + .alignleft, + .alignright { + display: block; + float: none; + margin-left: 0; + margin-right: 0; + } + /* Make sure the post-post navigation doesn't collide with anything */ + #nav-single { + display: block; + /*position: static; */ + } + .singular .hentry { + padding: 1.625em 0 0; + } + .singular.page .hentry { + padding: 1.625em 0 0; + } + /* Talking avatars take up too much room at this size */ + .commentlist > li.comment, + .commentlist > li.pingback { + margin-left: 0 !important; + } + .commentlist .avatar { + background: transparent; + display: block; + padding: 0; + position: static; + } + .commentlist .children .avatar { + background: none; + left: 2.2em; + padding: 0; + position: absolute; + top: 2.2em; + } + /* Use the available space in the smaller comment form */ + #respond input[type="text"] { + width: 95%; + } + #respond .comment-form-author .required, + #respond .comment-form-email .required { + left: 95%; + } + #content .gallery-columns-3 .gallery-item { + width: 31%; + padding-right: 2%; + } + #content .gallery-columns-3 .gallery-item img { + width: 100%; + height: auto; + } + +} +@media (max-width: 28em) { /* approx 450px */ + #content .gallery-columns-2 .gallery-item { + width: 45%; + padding-right: 4%; + } + #content .gallery-columns-2 .gallery-item img { + width: 100%; + height: auto; + } + +} + +/* =Print +----------------------------------------------- */ + +@media print { + body { + background: none !important; + font-size: 10pt; + color:black; + margin:0 + } + h1, h2, h3, h4, h5, h6 { + page-break-after:avoid; + page-break-inside:avoid + } + img { + page-break-inside:avoid; + page-break-after:avoid + } + blockquote, table, pre { page-break-inside:avoid } + ul, ol, dl { page-break-before:avoid } + + footer.entry-meta a[rel=bookmark]:link:after, + footer.entry-meta a[rel=bookmark]:visited:after { + content: " [" attr(href) "] "; /* Show URLs */ + } + #page { + clear: both !important; + display: block !important; + float: none !important; + max-width: 100%; + position: relative !important; + } + #branding { + border-top: none !important; + padding: 0; + } + #sitename { + margin: 0; + } + #site-title a { + font-size: 21pt; + } + #site-description { + font-size: 10pt; + } + #branding #searchform { + display: none; + } + #branding img { + display: none; + } + #access { + display: none; + } + #main { + border-top: none; + box-shadow: none; + } + #primary { + float: left; + margin: 0; + width: 100%; + } + #content { + margin: 0; + width: auto; + } + .singular #content { + margin: 0; + width: 100%; + } + .singular .entry-header .entry-meta { + position: static; + } + .entry-meta .edit-link a { + display: none; + } + #content nav { + display: none; + } + .breadcrumbs { + display: none; + } + .singular .entry-header, + .singular .entry-content, + .singular footer.entry-meta, + .singular #comments-title { + margin: 0; + width: 100%; + } + .singular .hentry { + padding: 0; + } + .entry-title, + .singular .entry-title { + font-size: 21pt; + } + .entry-meta { + font-size: 10pt; + } + .page-link { + display: none; + } + .singular #author-info { + background: none; + border-bottom: none; + border-top: none; + margin: 2.2em 0 0; + padding: 0; + } + #respond { + display: none; + } + .widget-area { + display: none; + } + #colophon { + display: none; + } + + /* Comments */ + #comments { display: none; } +} + +/*-------------------------------------------------------------- +# Clearings +--------------------------------------------------------------*/ +.clear:before, +.clear:after { + content: ""; + display: table; + table-layout: fixed; +} + +.clear:after { + clear: both; +} + +/** + * Featured Titles + */ +.featured-titles { + position: relative; + margin-bottom: 1em; +} + +/* Advanced Custom Fields Pro Thumbnail Gallery */ +.acf-pro-gallery { + list-style: none; + padding: 0; +} + +.acf-pro-gallery li { + display: inline-block; +} + +/* Global Header Navigation */ +.global-header-top { + background: #FFFFFF; +} + +.global-header-top .navbar { + margin-bottom: 0; + border: 0; + min-height: initial; + padding: 10px 0; +} + +.global-header-top .navbar-brand { + height: auto; + padding: 0; +} + +.global-header-top .navbar-form { + position: relative; + padding: 0; + min-width: 250px; + margin-bottom: 0; +} + +.global-header-top .navbar-form input[type="text"] { + width: 100%; +} + +.global-header-top .navbar-form button[type="submit"] { + border: 0; + position: absolute; + top: 1px; + right: 1px; +} + +.global-header-top .navbar-nav { + margin-bottom: 0; +} + +.global-header-top .navbar-nav > li > a { + font-weight: 700; +} + +.global-header-top .navbar-toggle { + margin: 0; + font-size: 24px; + border-radius: 0px; + border: 0; + line-height: 43px; + padding: 0; + width: 43px; +} + +#global-navigation { + padding-left: 0; +} + +@media (min-width: 768px) { + #global-navigation a, + #global-navigation a:visited { + background: #002d72; + color: #FFFFFF; + font-weight: 700; + } + + #global-navigation a:hover { + background: #004C9B; + color: #FFEE00; + } + + #global-navigation li + li { + margin-left: 10px; + } + + .global-header-top #global-navigation { + padding-right: 0; + margin-right: -15px; + } + + .global-header-top .navbar-form { + margin-top: 6px; + margin-bottom: 0; + } + + .global-header-top .navbar-nav > li > a { + line-height: 43px; + padding-top: 0; + padding-bottom: 0; + } + + .global-header-top .navbar-nav > li > a:hover { + background: #004C9B; + } +} + +.global-header-bottom { + background: #002d72; + font-weight: 700; +} + +.global-header-bottom .navbar-nav>li>a { + color: #FFFFFF; + padding: 10px 20px; +} + +.global-header-bottom .nav>li>a:hover { + color: #FFEE00; + background: #004C9B; +} + +.global-header-bottom .nav>li>a:focus { + background: #004C9B; +} + +.global-header-bottom .navbar-header { + position: relative; +} + +.global-header-bottom button.navbar-toggle { + color: #FFFFFF; + font-size: 1.5em; + position: absolute; + top: 0; + right: 0; + margin-bottom: 0; + margin-right: 8px; + margin-top: 4px; + border: none; +} + +.global-header-bottom button.navbar-toggle:focus { + background: none; +} + +.global-header-bottom button.navbar-toggle:hover { + background: none; + border: none; + color: #FFDC00; +} + +.global-header-bottom .navbar-brand { + color: #FFFFFF; + width: 100%; +} + +.global-header-bottom .current-menu-item, +.global-header-bottom .current-page-ancestor { + background: #004C9B; +} + +@media (min-width: 768px) { + .global-header-bottom .navbar-brand { + display: none; + } + + .global-header-bottom .navbar-nav { + text-align: center; + width: 100%; + } + + .global-header-bottom .navbar-nav>li { + float: none; + display: inline-block; + } +} + +/* Page header styles for mainsite (mimics local header styles) */ +.rula-page-header { + background: #0077C8; + box-shadow: inset 0 20px 10px -20px rgba(0,0,0,0.5); + margin-bottom: 1em; +} + +.rula-page-header h1, .rula-page-header .h1 { + color: #FFFFFF; + margin-top: 0.75em; + margin-bottom: 0.75em; + font-size: 36px; + display: block; +} + +/* Local header styles (subsites) */ +.local-header { + background: #0077C8; + box-shadow: inset 0 20px 10px -20px rgba(0,0,0,0.5); + margin-bottom: 1em; +} + +.local-header h1, .local-header .h1 { + margin-top: 0.75em; + margin-bottom: 0.75em; + font-size: 36px; + display: block; +} + +.local-header a { + color: #FFF; +} + +.local-header .rula_lh_donor { + display: block; + font-size: 0.65em; +} + +.local-header-navigation { + background: #FFFFFF; + font-weight: 700; +} + +.local-header-navigation a { + color: black; +} + +.local-header-navigation li.current-menu-item, +.local-header-navigation li.current-menu-parent { + background: #E6E6E6; +} + +.local-header-navigation .navbar-brand { + width: 100%; +} + +.local-header-navigation .navbar-brand:hover { + background: #E6E6E6; +} + +.local-header-navigation .navbar-header { + position: relative; +} + +.local-header-navigation button.navbar-toggle { + position: absolute; + font-size: 1.5em; + top: 0; + right: 0; + border: 0; + margin-bottom: 0; + margin-right: 8px; + margin-top: 4px; +} + +.local-header-navigation button.navbar-toggle:focus { + background: none; +} + +.local-header-navigation button.navbar-toggle:hover { + background: none; + font-weight: 700; +} + +@media (min-width: 768px) { + + .local-header-navigation .navbar-brand { + display: none; + } + + .local-header-navigation .navbar-nav { + text-align: center; + width: 100%; + } + + .local-header-navigation .navbar-nav > li { + display: inline-block; + float: none; + } +} + +.alert-message { + background: #FFA300; + font-size: 1.1em; + text-align: center; + padding: 0.5em 0; + font-weight: 700; +} + +/* Ryerson styles for footer */ +ul.social { + padding: 0; +} + +ul.social li { + display: inline-block; + font-size: 1.35em; + line-height: 30px; + width: 30px; + height: 30px; + text-align: center; +} + +ul.social li + li { + margin-left: 5px; +} + +ul.social a { + background: #000; + color: #FFF; + display: block; + border-radius: 30px; +} + +.local-footer { + background: #FFFFFF; + padding-top: 1em; + padding-bottom: 1em; +} + +.local-footer a { + text-decoration: underline; +} + +.local-footer ul.social a:hover, +.local-footer ul.social a:focus { + background: #0077C8; +} + +.local-footer-links ul { + padding: 0; + list-style: none; +} + +.local-footer-links li { + line-height: 1.6em; +} + +.local-footer .map { + max-width: 100%; +} + +.global-footer-top a, +.global-footer-top a:link, +.global-footer-top a:visited, +.global-footer-bottom a, +.global-footer-bottom a:link, +.global-footer-bottom a:visited { + color :#FFF; +} + +.global-footer-top a:hover, +.global-footer-top a:focus, +.global-footer-bottom a:hover, +.global-footer-bottom a:focus { + color: #FFDC00; +} + +.global-footer-top { + background: #002D72; + color: #FFFFFF; + padding-top: 1em; + padding-bottom: 1em; +} + +.global-footer-top ul.social li { +} + +.global-footer-top .social a { + background: #FFF; + color: #002d72; +} + +.global-footer-top ul.social a:hover, +.global-footer-top ul.social a:focus { + background: #FFDC00; +} + +.gft-top {} +.gft-middle { + margin-top: 1em; +} +.gft-bottom { + margin-top: 1em; +} + +.global-footer-links { + padding: 0; margin: 0; +} +.global-footer-links li { + display: inline-block; +} +.global-footer-links li + li:before { + content: "/\00a0"; + padding: 0 5px; + color: #CCC; +} + +.global-footer-bottom { + background: #004C9B; + color: #FFFFFF; + padding-top: 1em; + padding-bottom: 1em; +} + +/* Ryerson Graphic Device */ +.ryerson-gd { + background: #FFF; +} + +.ryerson-gd h3 { + display: inline-block; + background: #FFEE00; + padding: 1em; + margin: 0; + margin-top: -26px; + vertical-align: top; + margin-left: 1em;; +} + +.ryerson-gd img { + min-width: 100%; +} + +.ryerson-gd p { + padding: 1em 1.5em; +} + +/* Ryerson button style */ +.ryerson-btn, +.ryerson-btn:link, +.ryerson-btn:visited { + color: #FFFFFF; +} + +.ryerson-btn { + font-weight: 700; + display: block; + padding: 1em; + background: #002D72; + text-decoration: none; +} + +.ryerson-btn:hover, +.ryerson-btn:focus { + background: #FCFCFC; + color: #2A2A2A; + text-decoration: underline; +} + +@media (min-width: 768px) { + .ryerson-btn { + margin-left: 10px; + margin-bottom: 10px; + box-shadow: -10px 10px 0 #5BC2F4; + } +} + +.ryerson-heading { + font-size: 1.25em; + margin-top: 0; + padding: 1em; + font-weight: 700; + background: #002D72; + color: #FFFFFF; + text-decoration: none; +} +@media (min-width: 768px) { + .ryerson-heading { + margin-left: 10px; + margin-bottom: 10px; + box-shadow: -10px 10px 0 #5BC2F4; + } +} + +ul.ryerson-list { + padding: 0; + list-style-type: none; +} + +.ryerson-list li { + margin-top: 10px; +} + +.ryerson-list li a { + background: #FCFCFC; + display: block; + padding: 10px 15px; + font-weight: 700; + color: #2B2B2B; +} + +.ryerson-list li a:hover { + background: #FFF; +} + +/* Collapse toggle button (might use this in other places) */ +a.collapse-toggle { + color: #FFF; + font-family: "FontAwesome"; +} + +a.collapse-toggle:after { + content: "\f077"; +} + +a.collapse-toggle.collapsed:after { + content: "\f078"; +} + +.post .entry-title, +.post .entry-title a { + color: #000; +} + +.post .entry-title { + background: #FFEE00; + display: inline-block; + padding: 0.5em 1em; + margin-left: 0.75em; + margin-right: 0.75em; + margin-top: 0; + font-size: 1.5em; +} + +.post .entry-content { + background: #FFF; + margin-top: -35px; + padding-top: 35px; + padding-left: 30px; + padding-right: 30px; + padding-bottom: 1em; +} + +/* Frontpage Slider Bootstrap Overrides */ +#front-page-carousel { + overflow: hidden; + padding-bottom: 30px; + margin-bottom: 1em; + margin-top: -1em; +} + +.carousel-inner { + overflow: visible; +} + +.carousel-control, .carousel-control:focus, .carousel-control:hover { + opacity: 1; +} + +.carousel-control.left, .carousel-control.right { + background: none; + width: 10%; +} + +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + background: #004c9a; + padding: 7px 5px; + width: auto; + height: auto; +} + +.carousel-control .glyphicon-chevron-left:hover, +.carousel-control .glyphicon-chevron-right:hover { + background: #FFFFFF; + color: #004c9a; +} + +.carousel-control .glyphicon-chevron-left { + left: 0; + margin-left: 0; + margin-top: -17px; + font-size: 1em; +} + +.carousel-control .glyphicon-chevron-right { + right: 0; + margin-right: 0; + margin-top: -17px; + font-size: 1em; +} + +.carousel-indicators { + bottom: -10px; +} + +.carousel-caption { + background: #ffdc00; + color: #000000; + padding: 15px 18px; + text-align: left; + text-shadow: none; + bottom: -15px; + margin-bottom: -15px; + padding-bottom: 25px; +} + +.carousel-caption h3 { + font-size: 1.3em; + margin-top: 0; + margin-bottom: 0; +} + +.carousel-caption h3 a, +.carousel-caption h3 a:hover, +.carousel-caption h3 a:focus { + color: #000; + text-decoration: none; +} + +.carousel-caption p { + font-size: 1.2em; + font-weight: 700; + margin-top: 10px; + margin-bottom: 0; +} + +.carousel-caption .btn { + position: absolute; + border: none; + color: #000000; + font-weight: 700; + bottom: -15px; +} + +.carousel-caption a.btn:visited { + color: #000000; +} + +.carousel-caption a.btn:focus, +.carousel-caption a.btn:hover { + background: #000000; + color: #FFFFFF; +} + +@media (min-width: 768px) { + #front-page-carousel { + padding-bottom: 0; + } + + .carousel-indicators { + bottom: 0; + margin-bottom: 0; + } + + .carousel-caption { + bottom: 30px; + margin-bottom: 0; + padding: 15px 18px; + left: 10%; + right: 52%; + } + + .carousel-caption.with-button{ + padding-bottom: 25px; + } +} + +@media (min-width: 992px) { + .carousel-caption { + right: 58%; + } + + .carousel-caption h3 { + font-size: 1.5em; + } +} + +#frontpage-carousel .carousel-caption { + background: #333; + right: 0; + left: 0; + padding-bottom: 5px; + padding-top: 5px; + bottom: 0; +} + +/* Simple Calendar */ +.simcal-filter + .simcal-filter { + margin-left: 5px; +} + +.simcal-calendar a.post-edit-link { + display: none; +} +.simcal-calendar:hover a.post-edit-link { + display: inline; + position: absolute; + top: 0; + right: 0; +} \ No newline at end of file diff --git a/assets/template/rula_covid/fonts/FontAwesome.otf b/assets/template/rula_covid/fonts/FontAwesome.otf new file mode 100644 index 0000000..401ec0f Binary files /dev/null and b/assets/template/rula_covid/fonts/FontAwesome.otf differ diff --git a/assets/template/rula_covid/fonts/fontawesome-webfont.eot b/assets/template/rula_covid/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/assets/template/rula_covid/fonts/fontawesome-webfont.eot differ diff --git a/assets/template/rula_covid/fonts/fontawesome-webfont.svg b/assets/template/rula_covid/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/assets/template/rula_covid/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/template/rula_covid/fonts/fontawesome-webfont.ttf b/assets/template/rula_covid/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/assets/template/rula_covid/fonts/fontawesome-webfont.ttf differ diff --git a/assets/template/rula_covid/fonts/fontawesome-webfont.woff b/assets/template/rula_covid/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/assets/template/rula_covid/fonts/fontawesome-webfont.woff differ diff --git a/assets/template/rula_covid/fonts/fontawesome-webfont.woff2 b/assets/template/rula_covid/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/assets/template/rula_covid/fonts/fontawesome-webfont.woff2 differ diff --git a/assets/template/rula_covid/images/icon-disabilities.png b/assets/template/rula_covid/images/icon-disabilities.png new file mode 100644 index 0000000..d43f505 Binary files /dev/null and b/assets/template/rula_covid/images/icon-disabilities.png differ diff --git a/assets/template/rula_covid/images/icon-faculty.png b/assets/template/rula_covid/images/icon-faculty.png new file mode 100644 index 0000000..07060a8 Binary files /dev/null and b/assets/template/rula_covid/images/icon-faculty.png differ diff --git a/assets/template/rula_covid/images/icon-grad.png b/assets/template/rula_covid/images/icon-grad.png new file mode 100644 index 0000000..446223b Binary files /dev/null and b/assets/template/rula_covid/images/icon-grad.png differ diff --git a/assets/template/rula_covid/images/icon-laptop.png b/assets/template/rula_covid/images/icon-laptop.png new file mode 100644 index 0000000..0dd4d23 Binary files /dev/null and b/assets/template/rula_covid/images/icon-laptop.png differ diff --git a/assets/template/rula_covid/images/rula-logo.png b/assets/template/rula_covid/images/rula-logo.png new file mode 100644 index 0000000..78205e3 Binary files /dev/null and b/assets/template/rula_covid/images/rula-logo.png differ diff --git a/assets/template/rula_covid/images/rula_map.png b/assets/template/rula_covid/images/rula_map.png new file mode 100644 index 0000000..ffa0a53 Binary files /dev/null and b/assets/template/rula_covid/images/rula_map.png differ diff --git a/assets/template/rula_covid/images/rylib_logo.svg b/assets/template/rula_covid/images/rylib_logo.svg new file mode 100644 index 0000000..f99a513 --- /dev/null +++ b/assets/template/rula_covid/images/rylib_logo.svg @@ -0,0 +1,54 @@ + + + \ No newline at end of file diff --git a/assets/template/rula_covid/js/bootstrap.min.js b/assets/template/rula_covid/js/bootstrap.min.js new file mode 100644 index 0000000..9bcd2fc --- /dev/null +++ b/assets/template/rula_covid/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/assets/template/rula_covid/phrases.php b/assets/template/rula_covid/phrases.php new file mode 100644 index 0000000..a489bbc --- /dev/null +++ b/assets/template/rula_covid/phrases.php @@ -0,0 +1,21 @@ + "Book a Study Space", + "BOOK A ROOM" + => "RESERVE STUDY SPACE", + "Book Room" + => "Reserve Space", + "Book a Study Room" + => "Book a Study Space", + "The Study Room Booking module collects only the minimum amount of information from users necessary to book a room - this includes name and email address. This information is stored on a secure site, and will not be used for any other purpose." + => "The Study Space Booking module collects only the minimum amount of information from users necessary to book a space - this includes name and email address. This information is stored on a secure site, and will not be used for any other purpose.", + "in the study rooms this week" + => "this week", + "Room Features" + => "Item features", + "Some rooms may not be available because you have reached your maximum daily/weekly limits" + => "Some spaces may not be available because you have reached your maximum daily/weekly limits", + "Booking Policy" + => "New Booking Policy", + ); \ No newline at end of file diff --git a/license.txt b/license.txt index dba13ed..0973fd3 100644 --- a/license.txt +++ b/license.txt @@ -1,661 +1,51 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. +Copyright (c) 2008 - 2014, EllisLab, Inc. +All rights reserved. + +This license is a legal agreement between you and EllisLab Inc. for the use +of CodeIgniter Software (the "Software"). By obtaining the Software you +agree to comply with the terms and conditions of this license. + +PERMITTED USE +You are permitted to use, copy, modify, and distribute the Software and its +documentation, with or without modification, for any purpose, provided that +the following conditions are met: + +1. A copy of this license agreement must be included with the distribution. + +2. Redistributions of source code must retain the above copyright notice in + all source code files. + +3. Redistributions in binary form must reproduce the above copyright notice + in the documentation and/or other materials provided with the distribution. + +4. Any files that have been modified must carry notices stating the nature + of the change and the names of those who changed them. + +5. Products derived from the Software must include an acknowledgment that + they are derived from CodeIgniter in their documentation and/or other + materials provided with the distribution. + +6. Products derived from the Software may not be called "CodeIgniter", + nor may "CodeIgniter" appear in their name, without prior written + permission from EllisLab, Inc. + +INDEMNITY +You agree to indemnify and hold harmless the authors of the Software and +any contributors for any direct, indirect, incidental, or consequential +third-party claims, actions or suits, as well as any related expenses, +liabilities, damages, settlements or fees arising from your use or misuse +of the Software, or a violation of any terms of this license. + +DISCLAIMER OF WARRANTY +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESSED OR +IMPLIED, INCLUDING, BUT NOT LIMITED TO, WARRANTIES OF QUALITY, PERFORMANCE, +NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. + +LIMITATIONS OF LIABILITY +YOU ASSUME ALL RISK ASSOCIATED WITH THE INSTALLATION AND USE OF THE SOFTWARE. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS OF THE SOFTWARE BE LIABLE +FOR CLAIMS, DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE. LICENSE HOLDERS ARE SOLELY RESPONSIBLE FOR DETERMINING THE +APPROPRIATENESS OF USE AND ASSUME ALL RISKS ASSOCIATED WITH ITS USE, INCLUDING +BUT NOT LIMITED TO THE RISKS OF PROGRAM ERRORS, DAMAGE TO EQUIPMENT, LOSS OF +DATA OR SOFTWARE PROGRAMS, OR UNAVAILABILITY OR INTERRUPTION OF OPERATIONS. diff --git a/phpCas/.gitattributes b/phpCas/.gitattributes new file mode 100644 index 0000000..3e28f4e --- /dev/null +++ b/phpCas/.gitattributes @@ -0,0 +1,7 @@ +/docs/ export-ignore +/test/ export-ignore +/utils/ export-ignore +/.buildpath export-ignore +/.gitignore export-ignore +/.project export-ignore +/.travis.yml export-ignore diff --git a/phpCas/CAS.php b/phpCas/CAS.php index 2bb3680..8cd549e 100644 --- a/phpCas/CAS.php +++ b/phpCas/CAS.php @@ -17,1942 +17,14 @@ * See the License for the specific language governing permissions and * limitations under the License. * + * PHP Version 5 * - * - * Interface class of the phpCAS library - * PHP Version 5 - * - * @file CAS/CAS.php + * @file CAS.php * @category Authentication * @package PhpCAS - * @author Pascal Aubry - * @author Olivier Berger - * @author Brett Bieber - * @author Joachim Fritschi * @author Adam Franco * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 * @link https://wiki.jasig.org/display/CASC/phpCAS - * @ingroup public - */ - - -// -// hack by Vangelis Haniotakis to handle the absence of $_SERVER['REQUEST_URI'] -// in IIS -// -if (php_sapi_name() != 'cli') { - if (!isset($_SERVER['REQUEST_URI'])) { - $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING']; - } -} - -// Add a E_USER_DEPRECATED for php versions <= 5.2 -if (!defined('E_USER_DEPRECATED')) { - define('E_USER_DEPRECATED', E_USER_NOTICE); -} - - -// ######################################################################## -// CONSTANTS -// ######################################################################## - -// ------------------------------------------------------------------------ -// CAS VERSIONS -// ------------------------------------------------------------------------ - -/** - * phpCAS version. accessible for the user by phpCAS::getVersion(). - */ -define('PHPCAS_VERSION', '1.3.4+'); - -/** - * @addtogroup public - * @{ - */ - -/** - * CAS version 1.0 - */ -define("CAS_VERSION_1_0", '1.0'); -/*! - * CAS version 2.0 -*/ -define("CAS_VERSION_2_0", '2.0'); -/** - * CAS version 3.0 - */ -define("CAS_VERSION_3_0", '3.0'); - -// ------------------------------------------------------------------------ -// SAML defines -// ------------------------------------------------------------------------ - -/** - * SAML protocol - */ -define("SAML_VERSION_1_1", 'S1'); - -/** - * XML header for SAML POST - */ -define("SAML_XML_HEADER", ''); - -/** - * SOAP envelope for SAML POST - */ -define("SAML_SOAP_ENV", ''); - -/** - * SOAP body for SAML POST - */ -define("SAML_SOAP_BODY", ''); - -/** - * SAMLP request - */ -define("SAMLP_REQUEST", ''); -define("SAMLP_REQUEST_CLOSE", ''); - -/** - * SAMLP artifact tag (for the ticket) - */ -define("SAML_ASSERTION_ARTIFACT", ''); - -/** - * SAMLP close - */ -define("SAML_ASSERTION_ARTIFACT_CLOSE", ''); - -/** - * SOAP body close - */ -define("SAML_SOAP_BODY_CLOSE", ''); - -/** - * SOAP envelope close - */ -define("SAML_SOAP_ENV_CLOSE", ''); - -/** - * SAML Attributes - */ -define("SAML_ATTRIBUTES", 'SAMLATTRIBS'); - -/** - * SAML Attributes - */ -define("DEFAULT_ERROR", 'Internal script failure'); - -/** @} */ -/** - * @addtogroup publicPGTStorage - * @{ - */ -// ------------------------------------------------------------------------ -// FILE PGT STORAGE -// ------------------------------------------------------------------------ -/** - * Default path used when storing PGT's to file - */ -define("CAS_PGT_STORAGE_FILE_DEFAULT_PATH", session_save_path()); -/** @} */ -// ------------------------------------------------------------------------ -// SERVICE ACCESS ERRORS -// ------------------------------------------------------------------------ -/** - * @addtogroup publicServices - * @{ - */ - -/** - * phpCAS::service() error code on success - */ -define("PHPCAS_SERVICE_OK", 0); -/** - * phpCAS::service() error code when the PT could not retrieve because - * the CAS server did not respond. - */ -define("PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE", 1); -/** - * phpCAS::service() error code when the PT could not retrieve because - * the response of the CAS server was ill-formed. - */ -define("PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE", 2); -/** - * phpCAS::service() error code when the PT could not retrieve because - * the CAS server did not want to. - */ -define("PHPCAS_SERVICE_PT_FAILURE", 3); -/** - * phpCAS::service() error code when the service was not available. - */ -define("PHPCAS_SERVICE_NOT_AVAILABLE", 4); - -// ------------------------------------------------------------------------ -// SERVICE TYPES -// ------------------------------------------------------------------------ -/** - * phpCAS::getProxiedService() type for HTTP GET - */ -define("PHPCAS_PROXIED_SERVICE_HTTP_GET", 'CAS_ProxiedService_Http_Get'); -/** - * phpCAS::getProxiedService() type for HTTP POST - */ -define("PHPCAS_PROXIED_SERVICE_HTTP_POST", 'CAS_ProxiedService_Http_Post'); -/** - * phpCAS::getProxiedService() type for IMAP - */ -define("PHPCAS_PROXIED_SERVICE_IMAP", 'CAS_ProxiedService_Imap'); - - -/** @} */ -// ------------------------------------------------------------------------ -// LANGUAGES -// ------------------------------------------------------------------------ -/** - * @addtogroup publicLang - * @{ - */ - -define("PHPCAS_LANG_ENGLISH", 'CAS_Languages_English'); -define("PHPCAS_LANG_FRENCH", 'CAS_Languages_French'); -define("PHPCAS_LANG_GREEK", 'CAS_Languages_Greek'); -define("PHPCAS_LANG_GERMAN", 'CAS_Languages_German'); -define("PHPCAS_LANG_JAPANESE", 'CAS_Languages_Japanese'); -define("PHPCAS_LANG_SPANISH", 'CAS_Languages_Spanish'); -define("PHPCAS_LANG_CATALAN", 'CAS_Languages_Catalan'); - -/** @} */ - -/** - * @addtogroup internalLang - * @{ - */ - -/** - * phpCAS default language (when phpCAS::setLang() is not used) - */ -define("PHPCAS_LANG_DEFAULT", PHPCAS_LANG_ENGLISH); - -/** @} */ -// ------------------------------------------------------------------------ -// DEBUG -// ------------------------------------------------------------------------ -/** - * @addtogroup publicDebug - * @{ - */ - -/** - * The default directory for the debug file under Unix. - */ -function gettmpdir() { -if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); } -if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); } -if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); } -return "/tmp"; -} -define('DEFAULT_DEBUG_DIR', gettmpdir()."/"); - -/** @} */ - -// include the class autoloader -require_once dirname(__FILE__) . '/CAS/Autoload.php'; - -/** - * The phpCAS class is a simple container for the phpCAS library. It provides CAS - * authentication for web applications written in PHP. - * - * @ingroup public - * @class phpCAS - * @category Authentication - * @package PhpCAS - * @author Pascal Aubry - * @author Olivier Berger - * @author Brett Bieber - * @author Joachim Fritschi - * @author Adam Franco - * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 - * @link https://wiki.jasig.org/display/CASC/phpCAS - */ - -class phpCAS -{ - - /** - * This variable is used by the interface class phpCAS. - * - * @var CAS_Client - * @hideinitializer - */ - private static $_PHPCAS_CLIENT; - - /** - * This variable is used to store where the initializer is called from - * (to print a comprehensive error in case of multiple calls). - * - * @hideinitializer - */ - private static $_PHPCAS_INIT_CALL; - - /** - * This variable is used to store phpCAS debug mode. - * - * @hideinitializer - */ - private static $_PHPCAS_DEBUG; - - /** - * This variable is used to enable verbose mode - * This pevents debug info to be show to the user. Since it's a security - * feature the default is false - * - * @hideinitializer - */ - private static $_PHPCAS_VERBOSE = false; - - - // ######################################################################## - // INITIALIZATION - // ######################################################################## - - /** - * @addtogroup publicInit - * @{ - */ - - /** - * phpCAS client initializer. - * - * @param string $server_version the version of the CAS server - * @param string $server_hostname the hostname of the CAS server - * @param string $server_port the port the CAS server is running on - * @param string $server_uri the URI the CAS server is responding on - * @param bool $changeSessionID Allow phpCAS to change the session_id (Single - * Sign Out/handleLogoutRequests is based on that change) - * - * @return a newly created CAS_Client object - * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be - * called, only once, and before all other methods (except phpCAS::getVersion() - * and phpCAS::setDebug()). - */ - public static function client($server_version, $server_hostname, - $server_port, $server_uri, $changeSessionID = true - ) { - phpCAS :: traceBegin(); - if (is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')'); - } - - // store where the initializer is called from - $dbg = debug_backtrace(); - self::$_PHPCAS_INIT_CALL = array ( - 'done' => true, - 'file' => $dbg[0]['file'], - 'line' => $dbg[0]['line'], - 'method' => __CLASS__ . '::' . __FUNCTION__ - ); - - // initialize the object $_PHPCAS_CLIENT - try { - self::$_PHPCAS_CLIENT = new CAS_Client( - $server_version, false, $server_hostname, $server_port, $server_uri, - $changeSessionID - ); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - phpCAS :: traceEnd(); - } - - /** - * phpCAS proxy initializer. - * - * @param string $server_version the version of the CAS server - * @param string $server_hostname the hostname of the CAS server - * @param string $server_port the port the CAS server is running on - * @param string $server_uri the URI the CAS server is responding on - * @param bool $changeSessionID Allow phpCAS to change the session_id (Single - * Sign Out/handleLogoutRequests is based on that change) - * - * @return a newly created CAS_Client object - * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be - * called, only once, and before all other methods (except phpCAS::getVersion() - * and phpCAS::setDebug()). - */ - public static function proxy($server_version, $server_hostname, - $server_port, $server_uri, $changeSessionID = true - ) { - phpCAS :: traceBegin(); - if (is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')'); - } - - // store where the initialzer is called from - $dbg = debug_backtrace(); - self::$_PHPCAS_INIT_CALL = array ( - 'done' => true, - 'file' => $dbg[0]['file'], - 'line' => $dbg[0]['line'], - 'method' => __CLASS__ . '::' . __FUNCTION__ - ); - - // initialize the object $_PHPCAS_CLIENT - try { - self::$_PHPCAS_CLIENT = new CAS_Client( - $server_version, true, $server_hostname, $server_port, $server_uri, - $changeSessionID - ); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - phpCAS :: traceEnd(); - } - - /** - * Answer whether or not the client or proxy has been initialized - * - * @return bool - */ - public static function isInitialized () - { - return (is_object(self::$_PHPCAS_CLIENT)); - } - - /** @} */ - // ######################################################################## - // DEBUGGING - // ######################################################################## - - /** - * @addtogroup publicDebug - * @{ - */ - - /** - * Set/unset debug mode - * - * @param string $filename the name of the file used for logging, or false - * to stop debugging. - * - * @return void - */ - public static function setDebug($filename = '') - { - if ($filename != false && gettype($filename) != 'string') { - phpCAS :: error('type mismatched for parameter $dbg (should be false or the name of the log file)'); - } - if ($filename === false) { - self::$_PHPCAS_DEBUG['filename'] = false; - - } else { - if (empty ($filename)) { - if (preg_match('/^Win.*/', getenv('OS'))) { - if (isset ($_ENV['TMP'])) { - $debugDir = $_ENV['TMP'] . '/'; - } else { - $debugDir = ''; - } - } else { - $debugDir = DEFAULT_DEBUG_DIR; - } - $filename = $debugDir . 'phpCAS.log'; - } - - if (empty (self::$_PHPCAS_DEBUG['unique_id'])) { - self::$_PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))), 0, 4); - } - - self::$_PHPCAS_DEBUG['filename'] = $filename; - self::$_PHPCAS_DEBUG['indent'] = 0; - - phpCAS :: trace('START ('.date("Y-m-d H:i:s").') phpCAS-' . PHPCAS_VERSION . ' ******************'); - } - } - - /** - * Enable verbose errors messages in the website output - * This is a security relevant since internal status info may leak an may - * help an attacker. Default is therefore false - * - * @param bool $verbose enable verbose output - * - * @return void - */ - public static function setVerbose($verbose) - { - if ($verbose === true) { - self::$_PHPCAS_VERBOSE = true; - } else { - self::$_PHPCAS_VERBOSE = false; - } - } - - - /** - * Show is verbose mode is on - * - * @return boot verbose - */ - public static function getVerbose() - { - return self::$_PHPCAS_VERBOSE; - } - - /** - * Logs a string in debug mode. - * - * @param string $str the string to write - * - * @return void - * @private - */ - public static function log($str) - { - $indent_str = "."; - - - if (!empty(self::$_PHPCAS_DEBUG['filename'])) { - // Check if file exists and modifiy file permissions to be only - // readable by the webserver - if (!file_exists(self::$_PHPCAS_DEBUG['filename'])) { - touch(self::$_PHPCAS_DEBUG['filename']); - // Chmod will fail on windows - @chmod(self::$_PHPCAS_DEBUG['filename'], 0600); - } - for ($i = 0; $i < self::$_PHPCAS_DEBUG['indent']; $i++) { - - $indent_str .= '| '; - } - // allow for multiline output with proper identing. Usefull for - // dumping cas answers etc. - $str2 = str_replace("\n", "\n" . self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str, $str); - error_log(self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str2 . "\n", 3, self::$_PHPCAS_DEBUG['filename']); - } - - } - - /** - * This method is used by interface methods to print an error and where the - * function was originally called from. - * - * @param string $msg the message to print - * - * @return void - * @private - */ - public static function error($msg) - { - phpCAS :: traceBegin(); - $dbg = debug_backtrace(); - $function = '?'; - $file = '?'; - $line = '?'; - if (is_array($dbg)) { - for ($i = 1; $i < sizeof($dbg); $i++) { - if (is_array($dbg[$i]) && isset($dbg[$i]['class']) ) { - if ($dbg[$i]['class'] == __CLASS__) { - $function = $dbg[$i]['function']; - $file = $dbg[$i]['file']; - $line = $dbg[$i]['line']; - } - } - } - } - if (self::$_PHPCAS_VERBOSE) { - echo "
\nphpCAS error: " . __CLASS__ . "::" . $function . '(): ' . htmlentities($msg) . " in " . $file . " on line " . $line . "
\n"; - } else { - echo "
\nError: ". DEFAULT_ERROR ."
\n"; - } - phpCAS :: trace($msg . ' in ' . $file . 'on line ' . $line ); - phpCAS :: traceEnd(); - - throw new CAS_GracefullTerminationException(__CLASS__ . "::" . $function . '(): ' . $msg); - } - - /** - * This method is used to log something in debug mode. - * - * @param string $str string to log - * - * @return void - */ - public static function trace($str) - { - $dbg = debug_backtrace(); - phpCAS :: log($str . ' [' . basename($dbg[0]['file']) . ':' . $dbg[0]['line'] . ']'); - } - - /** - * This method is used to indicate the start of the execution of a function - * in debug mode. - * - * @return void - */ - public static function traceBegin() - { - $dbg = debug_backtrace(); - $str = '=> '; - if (!empty ($dbg[1]['class'])) { - $str .= $dbg[1]['class'] . '::'; - } - $str .= $dbg[1]['function'] . '('; - if (is_array($dbg[1]['args'])) { - foreach ($dbg[1]['args'] as $index => $arg) { - if ($index != 0) { - $str .= ', '; - } - if (is_object($arg)) { - $str .= get_class($arg); - } else { - $str .= str_replace(array("\r\n", "\n", "\r"), "", var_export($arg, true)); - } - } - } - if (isset($dbg[1]['file'])) { - $file = basename($dbg[1]['file']); - } else { - $file = 'unknown_file'; - } - if (isset($dbg[1]['line'])) { - $line = $dbg[1]['line']; - } else { - $line = 'unknown_line'; - } - $str .= ') [' . $file . ':' . $line . ']'; - phpCAS :: log($str); - if (!isset(self::$_PHPCAS_DEBUG['indent'])) { - self::$_PHPCAS_DEBUG['indent'] = 0; - } else { - self::$_PHPCAS_DEBUG['indent']++; - } - } - - /** - * This method is used to indicate the end of the execution of a function in - * debug mode. - * - * @param string $res the result of the function - * - * @return void - */ - public static function traceEnd($res = '') - { - if (empty(self::$_PHPCAS_DEBUG['indent'])) { - self::$_PHPCAS_DEBUG['indent'] = 0; - } else { - self::$_PHPCAS_DEBUG['indent']--; - } - $dbg = debug_backtrace(); - $str = ''; - if (is_object($res)) { - $str .= '<= ' . get_class($res); - } else { - $str .= '<= ' . str_replace(array("\r\n", "\n", "\r"), "", var_export($res, true)); - } - - phpCAS :: log($str); - } - - /** - * This method is used to indicate the end of the execution of the program - * - * @return void - */ - public static function traceExit() - { - phpCAS :: log('exit()'); - while (self::$_PHPCAS_DEBUG['indent'] > 0) { - phpCAS :: log('-'); - self::$_PHPCAS_DEBUG['indent']--; - } - } - - /** @} */ - // ######################################################################## - // INTERNATIONALIZATION - // ######################################################################## - /** - * @addtogroup publicLang - * @{ - */ - - /** - * This method is used to set the language used by phpCAS. - * - * @param string $lang string representing the language. - * - * @return void - * - * @sa PHPCAS_LANG_FRENCH, PHPCAS_LANG_ENGLISH - * @note Can be called only once. - */ - public static function setLang($lang) - { - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setLang($lang); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** @} */ - // ######################################################################## - // VERSION - // ######################################################################## - /** - * @addtogroup public - * @{ - */ - - /** - * This method returns the phpCAS version. - * - * @return the phpCAS version. - */ - public static function getVersion() - { - return PHPCAS_VERSION; - } - - /** @} */ - // ######################################################################## - // HTML OUTPUT - // ######################################################################## - /** - * @addtogroup publicOutput - * @{ - */ - - /** - * This method sets the HTML header used for all outputs. - * - * @param string $header the HTML header. - * - * @return void - */ - public static function setHTMLHeader($header) - { - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setHTMLHeader($header); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** - * This method sets the HTML footer used for all outputs. - * - * @param string $footer the HTML footer. - * - * @return void - */ - public static function setHTMLFooter($footer) - { - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setHTMLFooter($footer); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** @} */ - // ######################################################################## - // PGT STORAGE - // ######################################################################## - /** - * @addtogroup publicPGTStorage - * @{ - */ - - /** - * This method can be used to set a custom PGT storage object. - * - * @param CAS_PGTStorage $storage a PGT storage object that inherits from the - * CAS_PGTStorage class - * - * @return void - */ - public static function setPGTStorage($storage) - { - phpCAS :: traceBegin(); - phpCAS::_validateProxyExists(); - - try { - self::$_PHPCAS_CLIENT->setPGTStorage($storage); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - phpCAS :: traceEnd(); - } - - /** - * This method is used to tell phpCAS to store the response of the - * CAS server to PGT requests in a database. - * - * @param string $dsn_or_pdo a dsn string to use for creating a PDO - * object or a PDO object - * @param string $username the username to use when connecting to the - * database - * @param string $password the password to use when connecting to the - * database - * @param string $table the table to use for storing and retrieving - * PGT's - * @param string $driver_options any driver options to use when connecting - * to the database - * - * @return void - */ - public static function setPGTStorageDb($dsn_or_pdo, $username='', - $password='', $table='', $driver_options=null - ) { - phpCAS :: traceBegin(); - phpCAS::_validateProxyExists(); - - try { - self::$_PHPCAS_CLIENT->setPGTStorageDb($dsn_or_pdo, $username, $password, $table, $driver_options); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - phpCAS :: traceEnd(); - } - - /** - * This method is used to tell phpCAS to store the response of the - * CAS server to PGT requests onto the filesystem. - * - * @param string $path the path where the PGT's should be stored - * - * @return void - */ - public static function setPGTStorageFile($path = '') - { - phpCAS :: traceBegin(); - phpCAS::_validateProxyExists(); - - try { - self::$_PHPCAS_CLIENT->setPGTStorageFile($path); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - phpCAS :: traceEnd(); - } - /** @} */ - // ######################################################################## - // ACCESS TO EXTERNAL SERVICES - // ######################################################################## - /** - * @addtogroup publicServices - * @{ - */ - - /** - * Answer a proxy-authenticated service handler. - * - * @param string $type The service type. One of - * PHPCAS_PROXIED_SERVICE_HTTP_GET; PHPCAS_PROXIED_SERVICE_HTTP_POST; - * PHPCAS_PROXIED_SERVICE_IMAP - * - * @return CAS_ProxiedService - * @throws InvalidArgumentException If the service type is unknown. - */ - public static function getProxiedService ($type) - { - phpCAS :: traceBegin(); - phpCAS::_validateProxyExists(); - - try { - $res = self::$_PHPCAS_CLIENT->getProxiedService($type); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - return $res; - } - - /** - * Initialize a proxied-service handler with the proxy-ticket it should use. - * - * @param CAS_ProxiedService $proxiedService Proxied Service Handler - * - * @return void - * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. - * The code of the Exception will be one of: - * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE - * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE - * PHPCAS_SERVICE_PT_FAILURE - */ - public static function initializeProxiedService (CAS_ProxiedService $proxiedService) - { - phpCAS::_validateProxyExists(); - - try { - self::$_PHPCAS_CLIENT->initializeProxiedService($proxiedService); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** - * This method is used to access an HTTP[S] service. - * - * @param string $url the service to access. - * @param string &$err_code an error code Possible values are - * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, - * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, - * PHPCAS_SERVICE_NOT_AVAILABLE. - * @param string &$output the output of the service (also used to give an - * error message on failure). - * - * @return bool true on success, false otherwise (in this later case, - * $err_code gives the reason why it failed and $output contains an error - * message). - */ - public static function serviceWeb($url, & $err_code, & $output) - { - phpCAS :: traceBegin(); - phpCAS::_validateProxyExists(); - - try { - $res = self::$_PHPCAS_CLIENT->serviceWeb($url, $err_code, $output); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd($res); - return $res; - } - - /** - * This method is used to access an IMAP/POP3/NNTP service. - * - * @param string $url a string giving the URL of the service, - * including the mailing box for IMAP URLs, as accepted by imap_open(). - * @param string $service a string giving for CAS retrieve Proxy ticket - * @param string $flags options given to imap_open(). - * @param string &$err_code an error code Possible values are - * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, - * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, - * PHPCAS_SERVICE_NOT_AVAILABLE. - * @param string &$err_msg an error message on failure - * @param string &$pt the Proxy Ticket (PT) retrieved from the CAS - * server to access the URL on success, false on error). - * - * @return object IMAP stream on success, false otherwise (in this later - * case, $err_code gives the reason why it failed and $err_msg contains an - * error message). - */ - public static function serviceMail($url, $service, $flags, & $err_code, & $err_msg, & $pt) - { - phpCAS :: traceBegin(); - phpCAS::_validateProxyExists(); - - try { - $res = self::$_PHPCAS_CLIENT->serviceMail($url, $service, $flags, $err_code, $err_msg, $pt); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd($res); - return $res; - } - - /** @} */ - // ######################################################################## - // AUTHENTICATION - // ######################################################################## - /** - * @addtogroup publicAuth - * @{ - */ - - /** - * Set the times authentication will be cached before really accessing the - * CAS server in gateway mode: - * - -1: check only once, and then never again (until you pree login) - * - 0: always check - * - n: check every "n" time - * - * @param int $n an integer. - * - * @return void - */ - public static function setCacheTimesForAuthRecheck($n) - { - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** - * Set a callback function to be run when a user authenticates. - * - * The callback function will be passed a $logoutTicket as its first - * parameter, followed by any $additionalArgs you pass. The $logoutTicket - * parameter is an opaque string that can be used to map the session-id to - * logout request in order to support single-signout in applications that - * manage their own sessions (rather than letting phpCAS start the session). - * - * phpCAS::forceAuthentication() will always exit and forward client unless - * they are already authenticated. To perform an action at the moment the user - * logs in (such as registering an account, performing logging, etc), register - * a callback function here. - * - * @param string $function Callback function - * @param array $additionalArgs optional array of arguments - * - * @return void - */ - public static function setPostAuthenticateCallback ($function, array $additionalArgs = array()) - { - phpCAS::_validateClientExists(); - - self::$_PHPCAS_CLIENT->setPostAuthenticateCallback($function, $additionalArgs); - } - - /** - * Set a callback function to be run when a single-signout request is - * received. The callback function will be passed a $logoutTicket as its - * first parameter, followed by any $additionalArgs you pass. The - * $logoutTicket parameter is an opaque string that can be used to map a - * session-id to the logout request in order to support single-signout in - * applications that manage their own sessions (rather than letting phpCAS - * start and destroy the session). - * - * @param string $function Callback function - * @param array $additionalArgs optional array of arguments - * - * @return void - */ - public static function setSingleSignoutCallback ($function, array $additionalArgs = array()) - { - phpCAS::_validateClientExists(); - - self::$_PHPCAS_CLIENT->setSingleSignoutCallback($function, $additionalArgs); - } - - /** - * This method is called to check if the user is already authenticated - * locally or has a global cas session. A already existing cas session is - * determined by a cas gateway call.(cas login call without any interactive - * prompt) - * - * @return true when the user is authenticated, false when a previous - * gateway login failed or the function will not return if the user is - * redirected to the cas server for a gateway login attempt - */ - public static function checkAuthentication() - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - $auth = self::$_PHPCAS_CLIENT->checkAuthentication(); - - // store where the authentication has been checked and the result - self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); - - phpCAS :: traceEnd($auth); - return $auth; - } - - /** - * This method is called to force authentication if the user was not already - * authenticated. If the user is not authenticated, halt by redirecting to - * the CAS server. - * - * @return bool Authentication - */ - public static function forceAuthentication() - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - $auth = self::$_PHPCAS_CLIENT->forceAuthentication(); - - // store where the authentication has been checked and the result - self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); - - /* if (!$auth) { - phpCAS :: trace('user is not authenticated, redirecting to the CAS server'); - self::$_PHPCAS_CLIENT->forceAuthentication(); - } else { - phpCAS :: trace('no need to authenticate (user `' . phpCAS :: getUser() . '\' is already authenticated)'); - }*/ - - phpCAS :: traceEnd(); - return $auth; - } - - /** - * This method is called to renew the authentication. - * - * @return void - **/ - public static function renewAuthentication() - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - $auth = self::$_PHPCAS_CLIENT->renewAuthentication(); - - // store where the authentication has been checked and the result - self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); - - //self::$_PHPCAS_CLIENT->renewAuthentication(); - phpCAS :: traceEnd(); - } - - /** - * This method is called to check if the user is authenticated (previously or by - * tickets given in the URL). - * - * @return true when the user is authenticated. - */ - public static function isAuthenticated() - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - // call the isAuthenticated method of the $_PHPCAS_CLIENT object - $auth = self::$_PHPCAS_CLIENT->isAuthenticated(); - - // store where the authentication has been checked and the result - self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); - - phpCAS :: traceEnd($auth); - return $auth; - } - - /** - * Checks whether authenticated based on $_SESSION. Useful to avoid - * server calls. - * - * @return bool true if authenticated, false otherwise. - * @since 0.4.22 by Brendan Arnold - */ - public static function isSessionAuthenticated() - { - phpCAS::_validateClientExists(); - - return (self::$_PHPCAS_CLIENT->isSessionAuthenticated()); - } - - /** - * This method returns the CAS user's login name. - * - * @return string the login name of the authenticated user - * @warning should only be called after phpCAS::forceAuthentication() - * or phpCAS::checkAuthentication(). - * */ - public static function getUser() - { - phpCAS::_validateClientExists(); - - try { - return self::$_PHPCAS_CLIENT->getUser(); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** - * Answer attributes about the authenticated user. - * - * @warning should only be called after phpCAS::forceAuthentication() - * or phpCAS::checkAuthentication(). - * - * @return array - */ - public static function getAttributes() - { - phpCAS::_validateClientExists(); - - try { - return self::$_PHPCAS_CLIENT->getAttributes(); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** - * Answer true if there are attributes for the authenticated user. - * - * @warning should only be called after phpCAS::forceAuthentication() - * or phpCAS::checkAuthentication(). - * - * @return bool - */ - public static function hasAttributes() - { - phpCAS::_validateClientExists(); - - try { - return self::$_PHPCAS_CLIENT->hasAttributes(); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** - * Answer true if an attribute exists for the authenticated user. - * - * @param string $key attribute name - * - * @return bool - * @warning should only be called after phpCAS::forceAuthentication() - * or phpCAS::checkAuthentication(). - */ - public static function hasAttribute($key) - { - phpCAS::_validateClientExists(); - - try { - return self::$_PHPCAS_CLIENT->hasAttribute($key); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** - * Answer an attribute for the authenticated user. - * - * @param string $key attribute name - * - * @return mixed string for a single value or an array if multiple values exist. - * @warning should only be called after phpCAS::forceAuthentication() - * or phpCAS::checkAuthentication(). - */ - public static function getAttribute($key) - { - phpCAS::_validateClientExists(); - - try { - return self::$_PHPCAS_CLIENT->getAttribute($key); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** - * Handle logout requests. - * - * @param bool $check_client additional safety check - * @param array $allowed_clients array of allowed clients - * - * @return void - */ - public static function handleLogoutRequests($check_client = true, $allowed_clients = false) - { - phpCAS::_validateClientExists(); - - return (self::$_PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients)); - } - - /** - * This method returns the URL to be used to login. - * or phpCAS::isAuthenticated(). - * - * @return the login name of the authenticated user - */ - public static function getServerLoginURL() - { - phpCAS::_validateClientExists(); - - return self::$_PHPCAS_CLIENT->getServerLoginURL(); - } - - /** - * Set the login URL of the CAS server. - * - * @param string $url the login URL - * - * @return void - * @since 0.4.21 by Wyman Chan - */ - public static function setServerLoginURL($url = '') - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setServerLoginURL($url); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - } - - /** - * Set the serviceValidate URL of the CAS server. - * Used only in CAS 1.0 validations - * - * @param string $url the serviceValidate URL - * - * @return void - */ - public static function setServerServiceValidateURL($url = '') - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setServerServiceValidateURL($url); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - } - - /** - * Set the proxyValidate URL of the CAS server. - * Used for all CAS 2.0 validations - * - * @param string $url the proxyValidate URL - * - * @return void - */ - public static function setServerProxyValidateURL($url = '') - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setServerProxyValidateURL($url); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - } - - /** - * Set the samlValidate URL of the CAS server. - * - * @param string $url the samlValidate URL - * - * @return void - */ - public static function setServerSamlValidateURL($url = '') - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setServerSamlValidateURL($url); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - } - - /** - * This method returns the URL to be used to login. - * or phpCAS::isAuthenticated(). - * - * @return the login name of the authenticated user - */ - public static function getServerLogoutURL() - { - phpCAS::_validateClientExists(); - - return self::$_PHPCAS_CLIENT->getServerLogoutURL(); - } - - /** - * Set the logout URL of the CAS server. - * - * @param string $url the logout URL - * - * @return void - * @since 0.4.21 by Wyman Chan - */ - public static function setServerLogoutURL($url = '') - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setServerLogoutURL($url); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - } - - /** - * This method is used to logout from CAS. - * - * @param string $params an array that contains the optional url and - * service parameters that will be passed to the CAS server - * - * @return void - */ - public static function logout($params = "") - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - $parsedParams = array (); - if ($params != "") { - if (is_string($params)) { - phpCAS :: error('method `phpCAS::logout($url)\' is now deprecated, use `phpCAS::logoutWithUrl($url)\' instead'); - } - if (!is_array($params)) { - phpCAS :: error('type mismatched for parameter $params (should be `array\')'); - } - foreach ($params as $key => $value) { - if ($key != "service" && $key != "url") { - phpCAS :: error('only `url\' and `service\' parameters are allowed for method `phpCAS::logout($params)\''); - } - $parsedParams[$key] = $value; - } - } - self::$_PHPCAS_CLIENT->logout($parsedParams); - // never reached - phpCAS :: traceEnd(); - } - - /** - * This method is used to logout from CAS. Halts by redirecting to the CAS - * server. - * - * @param string $service a URL that will be transmitted to the CAS server - * - * @return void - */ - public static function logoutWithRedirectService($service) - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - if (!is_string($service)) { - phpCAS :: error('type mismatched for parameter $service (should be `string\')'); - } - self::$_PHPCAS_CLIENT->logout(array ( "service" => $service )); - // never reached - phpCAS :: traceEnd(); - } - - /** - * This method is used to logout from CAS. Halts by redirecting to the CAS - * server. - * - * @param string $url a URL that will be transmitted to the CAS server - * - * @return void - * @deprecated The url parameter has been removed from the CAS server as of - * version 3.3.5.1 - */ - public static function logoutWithUrl($url) - { - trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED); - phpCAS :: traceBegin(); - if (!is_object(self::$_PHPCAS_CLIENT)) { - phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); - } - if (!is_string($url)) { - phpCAS :: error('type mismatched for parameter $url (should be `string\')'); - } - self::$_PHPCAS_CLIENT->logout(array ( "url" => $url )); - // never reached - phpCAS :: traceEnd(); - } - - /** - * This method is used to logout from CAS. Halts by redirecting to the CAS - * server. - * - * @param string $service a URL that will be transmitted to the CAS server - * @param string $url a URL that will be transmitted to the CAS server - * - * @return void - * - * @deprecated The url parameter has been removed from the CAS server as of - * version 3.3.5.1 - */ - public static function logoutWithRedirectServiceAndUrl($service, $url) - { - trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED); - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - if (!is_string($service)) { - phpCAS :: error('type mismatched for parameter $service (should be `string\')'); - } - if (!is_string($url)) { - phpCAS :: error('type mismatched for parameter $url (should be `string\')'); - } - self::$_PHPCAS_CLIENT->logout( - array ( - "service" => $service, - "url" => $url - ) - ); - // never reached - phpCAS :: traceEnd(); - } - - /** - * Set the fixed URL that will be used by the CAS server to transmit the - * PGT. When this method is not called, a phpCAS script uses its own URL - * for the callback. - * - * @param string $url the URL - * - * @return void - */ - public static function setFixedCallbackURL($url = '') - { - phpCAS :: traceBegin(); - phpCAS::_validateProxyExists(); - - try { - self::$_PHPCAS_CLIENT->setCallbackURL($url); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - } - - /** - * Set the fixed URL that will be set as the CAS service parameter. When this - * method is not called, a phpCAS script uses its own URL. - * - * @param string $url the URL - * - * @return void - */ - public static function setFixedServiceURL($url) - { - phpCAS :: traceBegin(); - phpCAS::_validateProxyExists(); - - try { - self::$_PHPCAS_CLIENT->setURL($url); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - } - - /** - * Get the URL that is set as the CAS service parameter. - * - * @return string Service Url - */ - public static function getServiceURL() - { - phpCAS::_validateProxyExists(); - return (self::$_PHPCAS_CLIENT->getURL()); - } - - /** - * Retrieve a Proxy Ticket from the CAS server. - * - * @param string $target_service Url string of service to proxy - * @param string &$err_code error code - * @param string &$err_msg error message - * - * @return string Proxy Ticket - */ - public static function retrievePT($target_service, & $err_code, & $err_msg) - { - phpCAS::_validateProxyExists(); - - try { - return (self::$_PHPCAS_CLIENT->retrievePT($target_service, $err_code, $err_msg)); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - } - - /** - * Set the certificate of the CAS server CA and if the CN should be properly - * verified. - * - * @param string $cert CA certificate file name - * @param bool $validate_cn Validate CN in certificate (default true) - * - * @return void - */ - public static function setCasServerCACert($cert, $validate_cn = true) - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->setCasServerCACert($cert, $validate_cn); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - } - - /** - * Set no SSL validation for the CAS server. - * - * @return void - */ - public static function setNoCasServerValidation() - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - phpCAS :: trace('You have configured no validation of the legitimacy of the cas server. This is not recommended for production use.'); - self::$_PHPCAS_CLIENT->setNoCasServerValidation(); - phpCAS :: traceEnd(); - } - - - /** - * Disable the removal of a CAS-Ticket from the URL when authenticating - * DISABLING POSES A SECURITY RISK: - * We normally remove the ticket by an additional redirect as a security - * precaution to prevent a ticket in the HTTP_REFERRER or be carried over in - * the URL parameter - * - * @return void - */ - public static function setNoClearTicketsFromUrl() - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - self::$_PHPCAS_CLIENT->setNoClearTicketsFromUrl(); - phpCAS :: traceEnd(); - } - - /** @} */ - - /** - * Change CURL options. - * CURL is used to connect through HTTPS to CAS server - * - * @param string $key the option key - * @param string $value the value to set - * - * @return void - */ - public static function setExtraCurlOption($key, $value) - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - self::$_PHPCAS_CLIENT->setExtraCurlOption($key, $value); - phpCAS :: traceEnd(); - } - - /** - * If you want your service to be proxied you have to enable it (default - * disabled) and define an accepable list of proxies that are allowed to - * proxy your service. - * - * Add each allowed proxy definition object. For the normal CAS_ProxyChain - * class, the constructor takes an array of proxies to match. The list is in - * reverse just as seen from the service. Proxies have to be defined in reverse - * from the service to the user. If a user hits service A and gets proxied via - * B to service C the list of acceptable on C would be array(B,A). The definition - * of an individual proxy can be either a string or a regexp (preg_match is used) - * that will be matched against the proxy list supplied by the cas server - * when validating the proxy tickets. The strings are compared starting from - * the beginning and must fully match with the proxies in the list. - * Example: - * phpCAS::allowProxyChain(new CAS_ProxyChain(array( - * 'https://app.example.com/' - * ))); - * phpCAS::allowProxyChain(new CAS_ProxyChain(array( - * '/^https:\/\/app[0-9]\.example\.com\/rest\//', - * 'http://client.example.com/' - * ))); - * - * For quick testing or in certain production screnarios you might want to - * allow allow any other valid service to proxy your service. To do so, add - * the "Any" chain: - * phpcas::allowProxyChain(new CAS_ProxyChain_Any); - * THIS SETTING IS HOWEVER NOT RECOMMENDED FOR PRODUCTION AND HAS SECURITY - * IMPLICATIONS: YOU ARE ALLOWING ANY SERVICE TO ACT ON BEHALF OF A USER - * ON THIS SERVICE. - * - * @param CAS_ProxyChain_Interface $proxy_chain A proxy-chain that will be - * matched against the proxies requesting access - * - * @return void - */ - public static function allowProxyChain(CAS_ProxyChain_Interface $proxy_chain) - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - if (self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_2_0 - && self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_3_0 - ) { - phpCAS :: error('this method can only be used with the cas 2.0/3.0 protocols'); - } - self::$_PHPCAS_CLIENT->getAllowedProxyChains()->allowProxyChain($proxy_chain); - phpCAS :: traceEnd(); - } - - /** - * Answer an array of proxies that are sitting in front of this application. - * This method will only return a non-empty array if we have received and - * validated a Proxy Ticket. - * - * @return array - * @access public - * @since 6/25/09 - */ - public static function getProxies () - { - phpCAS::_validateProxyExists(); - - return(self::$_PHPCAS_CLIENT->getProxies()); - } - - // ######################################################################## - // PGTIOU/PGTID and logoutRequest rebroadcasting - // ######################################################################## - - /** - * Add a pgtIou/pgtId and logoutRequest rebroadcast node. - * - * @param string $rebroadcastNodeUrl The rebroadcast node URL. Can be - * hostname or IP. - * - * @return void - */ - public static function addRebroadcastNode($rebroadcastNodeUrl) - { - phpCAS::traceBegin(); - phpCAS::log('rebroadcastNodeUrl:'.$rebroadcastNodeUrl); - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->addRebroadcastNode($rebroadcastNodeUrl); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS::traceEnd(); - } - - /** - * This method is used to add header parameters when rebroadcasting - * pgtIou/pgtId or logoutRequest. - * - * @param String $header Header to send when rebroadcasting. - * - * @return void - */ - public static function addRebroadcastHeader($header) - { - phpCAS :: traceBegin(); - phpCAS::_validateClientExists(); - - try { - self::$_PHPCAS_CLIENT->addRebroadcastHeader($header); - } catch (Exception $e) { - phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); - } - - phpCAS :: traceEnd(); - } - - /** - * Checks if a client already exists - * - * @throws CAS_OutOfSequenceBeforeClientException - * - * @return void - */ - private static function _validateClientExists() - { - if (!is_object(self::$_PHPCAS_CLIENT)) { - throw new CAS_OutOfSequenceBeforeClientException(); - } - } - - /** - * Checks of a proxy client aready exists - * - * @throws CAS_OutOfSequenceBeforeProxyException - * - * @return void - */ - private static function _validateProxyExists() - { - if (!is_object(self::$_PHPCAS_CLIENT)) { - throw new CAS_OutOfSequenceBeforeProxyException(); - } - } -} -// ######################################################################## -// DOCUMENTATION -// ######################################################################## - -// ######################################################################## -// MAIN PAGE - -/** - * @mainpage - * - * The following pages only show the source documentation. - * - */ - -// ######################################################################## -// MODULES DEFINITION - -/** @defgroup public User interface */ - -/** @defgroup publicInit Initialization - * @ingroup public */ - -/** @defgroup publicAuth Authentication - * @ingroup public */ - -/** @defgroup publicServices Access to external services - * @ingroup public */ - -/** @defgroup publicConfig Configuration - * @ingroup public */ - -/** @defgroup publicLang Internationalization - * @ingroup publicConfig */ - -/** @defgroup publicOutput HTML output - * @ingroup publicConfig */ - -/** @defgroup publicPGTStorage PGT storage - * @ingroup publicConfig */ - -/** @defgroup publicDebug Debugging - * @ingroup public */ - -/** @defgroup internal Implementation */ - -/** @defgroup internalAuthentication Authentication - * @ingroup internal */ - -/** @defgroup internalBasic CAS Basic client features (CAS 1.0, Service Tickets) - * @ingroup internal */ - -/** @defgroup internalProxy CAS Proxy features (CAS 2.0, Proxy Granting Tickets) - * @ingroup internal */ - -/** @defgroup internalSAML CAS SAML features (SAML 1.1) - * @ingroup internal */ - -/** @defgroup internalPGTStorage PGT storage - * @ingroup internalProxy */ - -/** @defgroup internalPGTStorageDb PGT storage in a database - * @ingroup internalPGTStorage */ - -/** @defgroup internalPGTStorageFile PGT storage on the filesystem - * @ingroup internalPGTStorage */ - -/** @defgroup internalCallback Callback from the CAS server - * @ingroup internalProxy */ - -/** @defgroup internalProxyServices Proxy other services - * @ingroup internalProxy */ - -/** @defgroup internalService CAS client features (CAS 2.0, Proxied service) - * @ingroup internal */ - -/** @defgroup internalConfig Configuration - * @ingroup internal */ - -/** @defgroup internalBehave Internal behaviour of phpCAS - * @ingroup internalConfig */ - -/** @defgroup internalOutput HTML output - * @ingroup internalConfig */ - -/** @defgroup internalLang Internationalization - * @ingroup internalConfig - * - * To add a new language: - * - 1. define a new constant PHPCAS_LANG_XXXXXX in CAS/CAS.php - * - 2. copy any file from CAS/languages to CAS/languages/XXXXXX.php - * - 3. Make the translations */ -/** @defgroup internalDebug Debugging - * @ingroup internal */ - -/** @defgroup internalMisc Miscellaneous - * @ingroup internal */ - -// ######################################################################## -// EXAMPLES - -/** - * @example example_simple.php - */ -/** - * @example example_service.php - */ -/** - * @example example_service_that_proxies.php - */ -/** - * @example example_service_POST.php - */ -/** - * @example example_proxy_serviceWeb.php - */ -/** - * @example example_proxy_serviceWeb_chaining.php - */ -/** - * @example example_proxy_POST.php - */ -/** - * @example example_proxy_GET.php - */ -/** - * @example example_lang.php - */ -/** - * @example example_html.php - */ -/** - * @example example_pgt_storage_file.php - */ -/** - * @example example_pgt_storage_db.php - */ -/** - * @example example_gateway.php - */ -/** - * @example example_logout.php - */ -/** - * @example example_rebroadcast.php - */ -/** - * @example example_custom_urls.php - */ -/** - * @example example_advanced_saml11.php - */ -?> +require_once dirname(__FILE__).'/source/CAS.php'; \ No newline at end of file diff --git a/phpCas/LICENSE b/phpCas/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/phpCas/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/phpCas/NOTICE b/phpCas/NOTICE new file mode 100644 index 0000000..70d9ffc --- /dev/null +++ b/phpCas/NOTICE @@ -0,0 +1,81 @@ +Copyright 2007-2011, JA-SIG, Inc. +This project includes software developed by Jasig. +http://www.jasig.org/ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +=========================================================================== + +Copyright © 2003-2007, The ESUP-Portail consortium + +Requirements for sources originally licensed under the New BSD License: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +- Neither the name of JA-SIG, Inc. nor the names of its contributors may be +used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +=========================================================================== + +Copyright (c) 2009, Regents of the University of Nebraska +All rights reserved. + +Requirements for CAS_Autloader originally licensed under the New BSD License: + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this list +of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, this +list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +Neither the name of the University of Nebraska nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/phpCas/README.md b/phpCas/README.md new file mode 100644 index 0000000..84ae1e3 --- /dev/null +++ b/phpCas/README.md @@ -0,0 +1,31 @@ +phpCAS +======= + +phpCAS is an authentication library that allows PHP applications to easily authenticate +users via a Central Authentication Service (CAS) server. + +Please see the phpCAS website for more information: + +https://wiki.jasig.org/display/CASC/phpCAS + +[![Build Status](https://travis-ci.org/apereo/phpCAS.png)](https://travis-ci.org/apereo/phpCAS) + + +LICENSE +------- + +Copyright 2007-2015, JA-SIG, Inc. +This project includes software developed by Jasig. +http://www.jasig.org/ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this software except in compliance with the License. +You may obtain a copy of the License at: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/phpCas/composer.json b/phpCas/composer.json new file mode 100644 index 0000000..603595b --- /dev/null +++ b/phpCas/composer.json @@ -0,0 +1,29 @@ +{ + "name": "apereo/phpcas", + "description": "Provides a simple API for authenticating users against a CAS server", + "keywords": ["cas", "jasig", "apereo"], + "homepage": "https://wiki.jasig.org/display/CASC/phpCAS", + "type": "library", + "license": "Apache-2.0", + "authors": [ + {"name": "Joachim Fritschi", "homepage": "https://wiki.jasig.org/display/~fritschi"}, + {"name": "Adam Franco", "homepage": "https://wiki.jasig.org/display/~adamfranco"} + ], + "require": { + "php": ">=5.4.0", + "ext-curl": "*" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.10" + }, + "autoload": { + "classmap": [ + "source/" + ] + }, + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + } +} diff --git a/phpCas/source/CAS.php b/phpCas/source/CAS.php new file mode 100644 index 0000000..d8fd805 --- /dev/null +++ b/phpCas/source/CAS.php @@ -0,0 +1,2015 @@ + + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * @ingroup public + */ + + +// +// hack by Vangelis Haniotakis to handle the absence of $_SERVER['REQUEST_URI'] +// in IIS +// +if (!isset($_SERVER['REQUEST_URI']) && isset($_SERVER['SCRIPT_NAME']) && isset($_SERVER['QUERY_STRING'])) { + $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING']; +} + +// Add a E_USER_DEPRECATED for php versions <= 5.2 +if (!defined('E_USER_DEPRECATED')) { + define('E_USER_DEPRECATED', E_USER_NOTICE); +} + + +// ######################################################################## +// CONSTANTS +// ######################################################################## + +// ------------------------------------------------------------------------ +// CAS VERSIONS +// ------------------------------------------------------------------------ + +/** + * phpCAS version. accessible for the user by phpCAS::getVersion(). + */ +define('PHPCAS_VERSION', '1.3.6'); + +/** + * @addtogroup public + * @{ + */ + +/** + * CAS version 1.0 + */ +define("CAS_VERSION_1_0", '1.0'); +/*! + * CAS version 2.0 +*/ +define("CAS_VERSION_2_0", '2.0'); +/** + * CAS version 3.0 + */ +define("CAS_VERSION_3_0", '3.0'); + +// ------------------------------------------------------------------------ +// SAML defines +// ------------------------------------------------------------------------ + +/** + * SAML protocol + */ +define("SAML_VERSION_1_1", 'S1'); + +/** + * XML header for SAML POST + */ +define("SAML_XML_HEADER", ''); + +/** + * SOAP envelope for SAML POST + */ +define("SAML_SOAP_ENV", ''); + +/** + * SOAP body for SAML POST + */ +define("SAML_SOAP_BODY", ''); + +/** + * SAMLP request + */ +define("SAMLP_REQUEST", ''); +define("SAMLP_REQUEST_CLOSE", ''); + +/** + * SAMLP artifact tag (for the ticket) + */ +define("SAML_ASSERTION_ARTIFACT", ''); + +/** + * SAMLP close + */ +define("SAML_ASSERTION_ARTIFACT_CLOSE", ''); + +/** + * SOAP body close + */ +define("SAML_SOAP_BODY_CLOSE", ''); + +/** + * SOAP envelope close + */ +define("SAML_SOAP_ENV_CLOSE", ''); + +/** + * SAML Attributes + */ +define("SAML_ATTRIBUTES", 'SAMLATTRIBS'); + +/** + * SAML Attributes + */ +define("DEFAULT_ERROR", 'Internal script failure'); + +/** @} */ +/** + * @addtogroup publicPGTStorage + * @{ + */ +// ------------------------------------------------------------------------ +// FILE PGT STORAGE +// ------------------------------------------------------------------------ +/** + * Default path used when storing PGT's to file + */ +define("CAS_PGT_STORAGE_FILE_DEFAULT_PATH", session_save_path()); +/** @} */ +// ------------------------------------------------------------------------ +// SERVICE ACCESS ERRORS +// ------------------------------------------------------------------------ +/** + * @addtogroup publicServices + * @{ + */ + +/** + * phpCAS::service() error code on success + */ +define("PHPCAS_SERVICE_OK", 0); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the CAS server did not respond. + */ +define("PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE", 1); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the response of the CAS server was ill-formed. + */ +define("PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE", 2); +/** + * phpCAS::service() error code when the PT could not retrieve because + * the CAS server did not want to. + */ +define("PHPCAS_SERVICE_PT_FAILURE", 3); +/** + * phpCAS::service() error code when the service was not available. + */ +define("PHPCAS_SERVICE_NOT_AVAILABLE", 4); + +// ------------------------------------------------------------------------ +// SERVICE TYPES +// ------------------------------------------------------------------------ +/** + * phpCAS::getProxiedService() type for HTTP GET + */ +define("PHPCAS_PROXIED_SERVICE_HTTP_GET", 'CAS_ProxiedService_Http_Get'); +/** + * phpCAS::getProxiedService() type for HTTP POST + */ +define("PHPCAS_PROXIED_SERVICE_HTTP_POST", 'CAS_ProxiedService_Http_Post'); +/** + * phpCAS::getProxiedService() type for IMAP + */ +define("PHPCAS_PROXIED_SERVICE_IMAP", 'CAS_ProxiedService_Imap'); + + +/** @} */ +// ------------------------------------------------------------------------ +// LANGUAGES +// ------------------------------------------------------------------------ +/** + * @addtogroup publicLang + * @{ + */ + +define("PHPCAS_LANG_ENGLISH", 'CAS_Languages_English'); +define("PHPCAS_LANG_FRENCH", 'CAS_Languages_French'); +define("PHPCAS_LANG_GREEK", 'CAS_Languages_Greek'); +define("PHPCAS_LANG_GERMAN", 'CAS_Languages_German'); +define("PHPCAS_LANG_JAPANESE", 'CAS_Languages_Japanese'); +define("PHPCAS_LANG_SPANISH", 'CAS_Languages_Spanish'); +define("PHPCAS_LANG_CATALAN", 'CAS_Languages_Catalan'); +define("PHPCAS_LANG_CHINESE_SIMPLIFIED", 'CAS_Languages_ChineseSimplified'); + +/** @} */ + +/** + * @addtogroup internalLang + * @{ + */ + +/** + * phpCAS default language (when phpCAS::setLang() is not used) + */ +define("PHPCAS_LANG_DEFAULT", PHPCAS_LANG_ENGLISH); + +/** @} */ +// ------------------------------------------------------------------------ +// DEBUG +// ------------------------------------------------------------------------ +/** + * @addtogroup publicDebug + * @{ + */ + +/** + * The default directory for the debug file under Unix. + */ +function gettmpdir() { +if (!empty($_ENV['TMP'])) { return realpath($_ENV['TMP']); } +if (!empty($_ENV['TMPDIR'])) { return realpath( $_ENV['TMPDIR']); } +if (!empty($_ENV['TEMP'])) { return realpath( $_ENV['TEMP']); } +return "/tmp"; +} +define('DEFAULT_DEBUG_DIR', gettmpdir()."/"); + +/** @} */ + +// include the class autoloader +require_once dirname(__FILE__) . '/CAS/Autoload.php'; + +/** + * The phpCAS class is a simple container for the phpCAS library. It provides CAS + * authentication for web applications written in PHP. + * + * @ingroup public + * @class phpCAS + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class phpCAS +{ + + /** + * This variable is used by the interface class phpCAS. + * + * @var CAS_Client + * @hideinitializer + */ + private static $_PHPCAS_CLIENT; + + /** + * This variable is used to store where the initializer is called from + * (to print a comprehensive error in case of multiple calls). + * + * @hideinitializer + */ + private static $_PHPCAS_INIT_CALL; + + /** + * This variable is used to store phpCAS debug mode. + * + * @hideinitializer + */ + private static $_PHPCAS_DEBUG; + + /** + * This variable is used to enable verbose mode + * This pevents debug info to be show to the user. Since it's a security + * feature the default is false + * + * @hideinitializer + */ + private static $_PHPCAS_VERBOSE = false; + + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * @addtogroup publicInit + * @{ + */ + + /** + * phpCAS client initializer. + * + * @param string $server_version the version of the CAS server + * @param string $server_hostname the hostname of the CAS server + * @param string $server_port the port the CAS server is running on + * @param string $server_uri the URI the CAS server is responding on + * @param bool $changeSessionID Allow phpCAS to change the session_id (Single + * Sign Out/handleLogoutRequests is based on that change) + * + * @return a newly created CAS_Client object + * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be + * called, only once, and before all other methods (except phpCAS::getVersion() + * and phpCAS::setDebug()). + */ + public static function client($server_version, $server_hostname, + $server_port, $server_uri, $changeSessionID = true + ) { + phpCAS :: traceBegin(); + if (is_object(self::$_PHPCAS_CLIENT)) { + phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')'); + } + + // store where the initializer is called from + $dbg = debug_backtrace(); + self::$_PHPCAS_INIT_CALL = array ( + 'done' => true, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__ . '::' . __FUNCTION__ + ); + + // initialize the object $_PHPCAS_CLIENT + try { + self::$_PHPCAS_CLIENT = new CAS_Client( + $server_version, false, $server_hostname, $server_port, $server_uri, + $changeSessionID + ); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * phpCAS proxy initializer. + * + * @param string $server_version the version of the CAS server + * @param string $server_hostname the hostname of the CAS server + * @param string $server_port the port the CAS server is running on + * @param string $server_uri the URI the CAS server is responding on + * @param bool $changeSessionID Allow phpCAS to change the session_id (Single + * Sign Out/handleLogoutRequests is based on that change) + * + * @return a newly created CAS_Client object + * @note Only one of the phpCAS::client() and phpCAS::proxy functions should be + * called, only once, and before all other methods (except phpCAS::getVersion() + * and phpCAS::setDebug()). + */ + public static function proxy($server_version, $server_hostname, + $server_port, $server_uri, $changeSessionID = true + ) { + phpCAS :: traceBegin(); + if (is_object(self::$_PHPCAS_CLIENT)) { + phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')'); + } + + // store where the initialzer is called from + $dbg = debug_backtrace(); + self::$_PHPCAS_INIT_CALL = array ( + 'done' => true, + 'file' => $dbg[0]['file'], + 'line' => $dbg[0]['line'], + 'method' => __CLASS__ . '::' . __FUNCTION__ + ); + + // initialize the object $_PHPCAS_CLIENT + try { + self::$_PHPCAS_CLIENT = new CAS_Client( + $server_version, true, $server_hostname, $server_port, $server_uri, + $changeSessionID + ); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * Answer whether or not the client or proxy has been initialized + * + * @return bool + */ + public static function isInitialized () + { + return (is_object(self::$_PHPCAS_CLIENT)); + } + + /** @} */ + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * @addtogroup publicDebug + * @{ + */ + + /** + * Set/unset debug mode + * + * @param string $filename the name of the file used for logging, or false + * to stop debugging. + * + * @return void + */ + public static function setDebug($filename = '') + { + if ($filename != false && gettype($filename) != 'string') { + phpCAS :: error('type mismatched for parameter $dbg (should be false or the name of the log file)'); + } + if ($filename === false) { + self::$_PHPCAS_DEBUG['filename'] = false; + + } else { + if (empty ($filename)) { + if (preg_match('/^Win.*/', getenv('OS'))) { + if (isset ($_ENV['TMP'])) { + $debugDir = $_ENV['TMP'] . '/'; + } else { + $debugDir = ''; + } + } else { + $debugDir = DEFAULT_DEBUG_DIR; + } + $filename = $debugDir . 'phpCAS.log'; + } + + if (empty (self::$_PHPCAS_DEBUG['unique_id'])) { + self::$_PHPCAS_DEBUG['unique_id'] = substr(strtoupper(md5(uniqid(''))), 0, 4); + } + + self::$_PHPCAS_DEBUG['filename'] = $filename; + self::$_PHPCAS_DEBUG['indent'] = 0; + + phpCAS :: trace('START ('.date("Y-m-d H:i:s").') phpCAS-' . PHPCAS_VERSION . ' ******************'); + } + } + + /** + * Enable verbose errors messages in the website output + * This is a security relevant since internal status info may leak an may + * help an attacker. Default is therefore false + * + * @param bool $verbose enable verbose output + * + * @return void + */ + public static function setVerbose($verbose) + { + if ($verbose === true) { + self::$_PHPCAS_VERBOSE = true; + } else { + self::$_PHPCAS_VERBOSE = false; + } + } + + + /** + * Show is verbose mode is on + * + * @return boot verbose + */ + public static function getVerbose() + { + return self::$_PHPCAS_VERBOSE; + } + + /** + * Logs a string in debug mode. + * + * @param string $str the string to write + * + * @return void + * @private + */ + public static function log($str) + { + $indent_str = "."; + + + if (!empty(self::$_PHPCAS_DEBUG['filename'])) { + // Check if file exists and modifiy file permissions to be only + // readable by the webserver + if (!file_exists(self::$_PHPCAS_DEBUG['filename'])) { + touch(self::$_PHPCAS_DEBUG['filename']); + // Chmod will fail on windows + @chmod(self::$_PHPCAS_DEBUG['filename'], 0600); + } + for ($i = 0; $i < self::$_PHPCAS_DEBUG['indent']; $i++) { + + $indent_str .= '| '; + } + // allow for multiline output with proper identing. Usefull for + // dumping cas answers etc. + $str2 = str_replace("\n", "\n" . self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str, $str); + error_log(self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str2 . "\n", 3, self::$_PHPCAS_DEBUG['filename']); + } + + } + + /** + * This method is used by interface methods to print an error and where the + * function was originally called from. + * + * @param string $msg the message to print + * + * @return void + * @private + */ + public static function error($msg) + { + phpCAS :: traceBegin(); + $dbg = debug_backtrace(); + $function = '?'; + $file = '?'; + $line = '?'; + if (is_array($dbg)) { + for ($i = 1; $i < sizeof($dbg); $i++) { + if (is_array($dbg[$i]) && isset($dbg[$i]['class']) ) { + if ($dbg[$i]['class'] == __CLASS__) { + $function = $dbg[$i]['function']; + $file = $dbg[$i]['file']; + $line = $dbg[$i]['line']; + } + } + } + } + if (self::$_PHPCAS_VERBOSE) { + echo "
\nphpCAS error: " . __CLASS__ . "::" . $function . '(): ' . htmlentities($msg) . " in " . $file . " on line " . $line . "
\n"; + } else { + echo "
\nError: ". DEFAULT_ERROR ."
\n"; + } + phpCAS :: trace($msg . ' in ' . $file . 'on line ' . $line ); + phpCAS :: traceEnd(); + + throw new CAS_GracefullTerminationException(__CLASS__ . "::" . $function . '(): ' . $msg); + } + + /** + * This method is used to log something in debug mode. + * + * @param string $str string to log + * + * @return void + */ + public static function trace($str) + { + $dbg = debug_backtrace(); + phpCAS :: log($str . ' [' . basename($dbg[0]['file']) . ':' . $dbg[0]['line'] . ']'); + } + + /** + * This method is used to indicate the start of the execution of a function + * in debug mode. + * + * @return void + */ + public static function traceBegin() + { + $dbg = debug_backtrace(); + $str = '=> '; + if (!empty ($dbg[1]['class'])) { + $str .= $dbg[1]['class'] . '::'; + } + $str .= $dbg[1]['function'] . '('; + if (is_array($dbg[1]['args'])) { + foreach ($dbg[1]['args'] as $index => $arg) { + if ($index != 0) { + $str .= ', '; + } + if (is_object($arg)) { + $str .= get_class($arg); + } else { + $str .= str_replace(array("\r\n", "\n", "\r"), "", var_export($arg, true)); + } + } + } + if (isset($dbg[1]['file'])) { + $file = basename($dbg[1]['file']); + } else { + $file = 'unknown_file'; + } + if (isset($dbg[1]['line'])) { + $line = $dbg[1]['line']; + } else { + $line = 'unknown_line'; + } + $str .= ') [' . $file . ':' . $line . ']'; + phpCAS :: log($str); + if (!isset(self::$_PHPCAS_DEBUG['indent'])) { + self::$_PHPCAS_DEBUG['indent'] = 0; + } else { + self::$_PHPCAS_DEBUG['indent']++; + } + } + + /** + * This method is used to indicate the end of the execution of a function in + * debug mode. + * + * @param string $res the result of the function + * + * @return void + */ + public static function traceEnd($res = '') + { + if (empty(self::$_PHPCAS_DEBUG['indent'])) { + self::$_PHPCAS_DEBUG['indent'] = 0; + } else { + self::$_PHPCAS_DEBUG['indent']--; + } + $dbg = debug_backtrace(); + $str = ''; + if (is_object($res)) { + $str .= '<= ' . get_class($res); + } else { + $str .= '<= ' . str_replace(array("\r\n", "\n", "\r"), "", var_export($res, true)); + } + + phpCAS :: log($str); + } + + /** + * This method is used to indicate the end of the execution of the program + * + * @return void + */ + public static function traceExit() + { + phpCAS :: log('exit()'); + while (self::$_PHPCAS_DEBUG['indent'] > 0) { + phpCAS :: log('-'); + self::$_PHPCAS_DEBUG['indent']--; + } + } + + /** @} */ + // ######################################################################## + // INTERNATIONALIZATION + // ######################################################################## + /** + * @addtogroup publicLang + * @{ + */ + + /** + * This method is used to set the language used by phpCAS. + * + * @param string $lang string representing the language. + * + * @return void + * + * @sa PHPCAS_LANG_FRENCH, PHPCAS_LANG_ENGLISH + * @note Can be called only once. + */ + public static function setLang($lang) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setLang($lang); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** @} */ + // ######################################################################## + // VERSION + // ######################################################################## + /** + * @addtogroup public + * @{ + */ + + /** + * This method returns the phpCAS version. + * + * @return the phpCAS version. + */ + public static function getVersion() + { + return PHPCAS_VERSION; + } + + /** @} */ + // ######################################################################## + // HTML OUTPUT + // ######################################################################## + /** + * @addtogroup publicOutput + * @{ + */ + + /** + * This method sets the HTML header used for all outputs. + * + * @param string $header the HTML header. + * + * @return void + */ + public static function setHTMLHeader($header) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setHTMLHeader($header); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * This method sets the HTML footer used for all outputs. + * + * @param string $footer the HTML footer. + * + * @return void + */ + public static function setHTMLFooter($footer) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setHTMLFooter($footer); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** @} */ + // ######################################################################## + // PGT STORAGE + // ######################################################################## + /** + * @addtogroup publicPGTStorage + * @{ + */ + + /** + * This method can be used to set a custom PGT storage object. + * + * @param CAS_PGTStorage $storage a PGT storage object that inherits from the + * CAS_PGTStorage class + * + * @return void + */ + public static function setPGTStorage($storage) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorage($storage); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests in a database. + * + * @param string $dsn_or_pdo a dsn string to use for creating a PDO + * object or a PDO object + * @param string $username the username to use when connecting to the + * database + * @param string $password the password to use when connecting to the + * database + * @param string $table the table to use for storing and retrieving + * PGT's + * @param string $driver_options any driver options to use when connecting + * to the database + * + * @return void + */ + public static function setPGTStorageDb($dsn_or_pdo, $username='', + $password='', $table='', $driver_options=null + ) { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorageDb($dsn_or_pdo, $username, $password, $table, $driver_options); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests onto the filesystem. + * + * @param string $path the path where the PGT's should be stored + * + * @return void + */ + public static function setPGTStorageFile($path = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setPGTStorageFile($path); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + phpCAS :: traceEnd(); + } + /** @} */ + // ######################################################################## + // ACCESS TO EXTERNAL SERVICES + // ######################################################################## + /** + * @addtogroup publicServices + * @{ + */ + + /** + * Answer a proxy-authenticated service handler. + * + * @param string $type The service type. One of + * PHPCAS_PROXIED_SERVICE_HTTP_GET; PHPCAS_PROXIED_SERVICE_HTTP_POST; + * PHPCAS_PROXIED_SERVICE_IMAP + * + * @return CAS_ProxiedService + * @throws InvalidArgumentException If the service type is unknown. + */ + public static function getProxiedService ($type) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + $res = self::$_PHPCAS_CLIENT->getProxiedService($type); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + return $res; + } + + /** + * Initialize a proxied-service handler with the proxy-ticket it should use. + * + * @param CAS_ProxiedService $proxiedService Proxied Service Handler + * + * @return void + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + */ + public static function initializeProxiedService (CAS_ProxiedService $proxiedService) + { + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->initializeProxiedService($proxiedService); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * This method is used to access an HTTP[S] service. + * + * @param string $url the service to access. + * @param string &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$output the output of the service (also used to give an + * error message on failure). + * + * @return bool true on success, false otherwise (in this later case, + * $err_code gives the reason why it failed and $output contains an error + * message). + */ + public static function serviceWeb($url, & $err_code, & $output) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + $res = self::$_PHPCAS_CLIENT->serviceWeb($url, $err_code, $output); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd($res); + return $res; + } + + /** + * This method is used to access an IMAP/POP3/NNTP service. + * + * @param string $url a string giving the URL of the service, + * including the mailing box for IMAP URLs, as accepted by imap_open(). + * @param string $service a string giving for CAS retrieve Proxy ticket + * @param string $flags options given to imap_open(). + * @param string &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$err_msg an error message on failure + * @param string &$pt the Proxy Ticket (PT) retrieved from the CAS + * server to access the URL on success, false on error). + * + * @return object IMAP stream on success, false otherwise (in this later + * case, $err_code gives the reason why it failed and $err_msg contains an + * error message). + */ + public static function serviceMail($url, $service, $flags, & $err_code, & $err_msg, & $pt) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + $res = self::$_PHPCAS_CLIENT->serviceMail($url, $service, $flags, $err_code, $err_msg, $pt); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd($res); + return $res; + } + + /** @} */ + // ######################################################################## + // AUTHENTICATION + // ######################################################################## + /** + * @addtogroup publicAuth + * @{ + */ + + /** + * Set the times authentication will be cached before really accessing the + * CAS server in gateway mode: + * - -1: check only once, and then never again (until you pree login) + * - 0: always check + * - n: check every "n" time + * + * @param int $n an integer. + * + * @return void + */ + public static function setCacheTimesForAuthRecheck($n) + { + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setCacheTimesForAuthRecheck($n); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + + /** + * Set a callback function to be run when receiving CAS attributes + * + * The callback function will be passed an $success_elements + * payload of the response (\DOMElement) as its first parameter. + * + * @param string $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setCasAttributeParserCallback($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setCasAttributeParserCallback($function, $additionalArgs); + } + + /** + * Set a callback function to be run when a user authenticates. + * + * The callback function will be passed a $logoutTicket as its first + * parameter, followed by any $additionalArgs you pass. The $logoutTicket + * parameter is an opaque string that can be used to map the session-id to + * logout request in order to support single-signout in applications that + * manage their own sessions (rather than letting phpCAS start the session). + * + * phpCAS::forceAuthentication() will always exit and forward client unless + * they are already authenticated. To perform an action at the moment the user + * logs in (such as registering an account, performing logging, etc), register + * a callback function here. + * + * @param string $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setPostAuthenticateCallback ($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setPostAuthenticateCallback($function, $additionalArgs); + } + + /** + * Set a callback function to be run when a single-signout request is + * received. The callback function will be passed a $logoutTicket as its + * first parameter, followed by any $additionalArgs you pass. The + * $logoutTicket parameter is an opaque string that can be used to map a + * session-id to the logout request in order to support single-signout in + * applications that manage their own sessions (rather than letting phpCAS + * start and destroy the session). + * + * @param string $function Callback function + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public static function setSingleSignoutCallback ($function, array $additionalArgs = array()) + { + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setSingleSignoutCallback($function, $additionalArgs); + } + + /** + * This method is called to check if the user is already authenticated + * locally or has a global cas session. A already existing cas session is + * determined by a cas gateway call.(cas login call without any interactive + * prompt) + * + * @return true when the user is authenticated, false when a previous + * gateway login failed or the function will not return if the user is + * redirected to the cas server for a gateway login attempt + */ + public static function checkAuthentication() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + $auth = self::$_PHPCAS_CLIENT->checkAuthentication(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + phpCAS :: traceEnd($auth); + return $auth; + } + + /** + * This method is called to force authentication if the user was not already + * authenticated. If the user is not authenticated, halt by redirecting to + * the CAS server. + * + * @return bool Authentication + */ + public static function forceAuthentication() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + $auth = self::$_PHPCAS_CLIENT->forceAuthentication(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + /* if (!$auth) { + phpCAS :: trace('user is not authenticated, redirecting to the CAS server'); + self::$_PHPCAS_CLIENT->forceAuthentication(); + } else { + phpCAS :: trace('no need to authenticate (user `' . phpCAS :: getUser() . '\' is already authenticated)'); + }*/ + + phpCAS :: traceEnd(); + return $auth; + } + + /** + * This method is called to renew the authentication. + * + * @return void + **/ + public static function renewAuthentication() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + $auth = self::$_PHPCAS_CLIENT->renewAuthentication(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + //self::$_PHPCAS_CLIENT->renewAuthentication(); + phpCAS :: traceEnd(); + } + + /** + * This method is called to check if the user is authenticated (previously or by + * tickets given in the URL). + * + * @return true when the user is authenticated. + */ + public static function isAuthenticated() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + // call the isAuthenticated method of the $_PHPCAS_CLIENT object + $auth = self::$_PHPCAS_CLIENT->isAuthenticated(); + + // store where the authentication has been checked and the result + self::$_PHPCAS_CLIENT->markAuthenticationCall($auth); + + phpCAS :: traceEnd($auth); + return $auth; + } + + /** + * Checks whether authenticated based on $_SESSION. Useful to avoid + * server calls. + * + * @return bool true if authenticated, false otherwise. + * @since 0.4.22 by Brendan Arnold + */ + public static function isSessionAuthenticated() + { + phpCAS::_validateClientExists(); + + return (self::$_PHPCAS_CLIENT->isSessionAuthenticated()); + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * */ + public static function getUser() + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getUser(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer attributes about the authenticated user. + * + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * + * @return array + */ + public static function getAttributes() + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getAttributes(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer true if there are attributes for the authenticated user. + * + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + * + * @return bool + */ + public static function hasAttributes() + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->hasAttributes(); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer true if an attribute exists for the authenticated user. + * + * @param string $key attribute name + * + * @return bool + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + */ + public static function hasAttribute($key) + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->hasAttribute($key); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Answer an attribute for the authenticated user. + * + * @param string $key attribute name + * + * @return mixed string for a single value or an array if multiple values exist. + * @warning should only be called after phpCAS::forceAuthentication() + * or phpCAS::checkAuthentication(). + */ + public static function getAttribute($key) + { + phpCAS::_validateClientExists(); + + try { + return self::$_PHPCAS_CLIENT->getAttribute($key); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Handle logout requests. + * + * @param bool $check_client additional safety check + * @param array $allowed_clients array of allowed clients + * + * @return void + */ + public static function handleLogoutRequests($check_client = true, $allowed_clients = false) + { + phpCAS::_validateClientExists(); + + return (self::$_PHPCAS_CLIENT->handleLogoutRequests($check_client, $allowed_clients)); + } + + /** + * This method returns the URL to be used to login. + * or phpCAS::isAuthenticated(). + * + * @return the login name of the authenticated user + */ + public static function getServerLoginURL() + { + phpCAS::_validateClientExists(); + + return self::$_PHPCAS_CLIENT->getServerLoginURL(); + } + + /** + * Set the login URL of the CAS server. + * + * @param string $url the login URL + * + * @return void + * @since 0.4.21 by Wyman Chan + */ + public static function setServerLoginURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerLoginURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the serviceValidate URL of the CAS server. + * Used for all CAS versions of URL validations. + * Examples: + * CAS 1.0 http://www.exemple.com/validate + * CAS 2.0 http://www.exemple.com/validateURL + * CAS 3.0 http://www.exemple.com/p3/serviceValidate + * + * @param string $url the serviceValidate URL + * + * @return void + */ + public static function setServerServiceValidateURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerServiceValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the proxyValidate URL of the CAS server. + * Used for all CAS versions of proxy URL validations + * Examples: + * CAS 1.0 http://www.exemple.com/ + * CAS 2.0 http://www.exemple.com/proxyValidate + * CAS 3.0 http://www.exemple.com/p3/proxyValidate + * + * @param string $url the proxyValidate URL + * + * @return void + */ + public static function setServerProxyValidateURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerProxyValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the samlValidate URL of the CAS server. + * + * @param string $url the samlValidate URL + * + * @return void + */ + public static function setServerSamlValidateURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerSamlValidateURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * This method returns the URL to be used to login. + * or phpCAS::isAuthenticated(). + * + * @return the login name of the authenticated user + */ + public static function getServerLogoutURL() + { + phpCAS::_validateClientExists(); + + return self::$_PHPCAS_CLIENT->getServerLogoutURL(); + } + + /** + * Set the logout URL of the CAS server. + * + * @param string $url the logout URL + * + * @return void + * @since 0.4.21 by Wyman Chan + */ + public static function setServerLogoutURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setServerLogoutURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. + * + * @param string $params an array that contains the optional url and + * service parameters that will be passed to the CAS server + * + * @return void + */ + public static function logout($params = "") + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + $parsedParams = array (); + if ($params != "") { + if (is_string($params)) { + phpCAS :: error('method `phpCAS::logout($url)\' is now deprecated, use `phpCAS::logoutWithUrl($url)\' instead'); + } + if (!is_array($params)) { + phpCAS :: error('type mismatched for parameter $params (should be `array\')'); + } + foreach ($params as $key => $value) { + if ($key != "service" && $key != "url") { + phpCAS :: error('only `url\' and `service\' parameters are allowed for method `phpCAS::logout($params)\''); + } + $parsedParams[$key] = $value; + } + } + self::$_PHPCAS_CLIENT->logout($parsedParams); + // never reached + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS + * server. + * + * @param string $service a URL that will be transmitted to the CAS server + * + * @return void + */ + public static function logoutWithRedirectService($service) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + if (!is_string($service)) { + phpCAS :: error('type mismatched for parameter $service (should be `string\')'); + } + self::$_PHPCAS_CLIENT->logout(array ( "service" => $service )); + // never reached + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS + * server. + * + * @param string $url a URL that will be transmitted to the CAS server + * + * @return void + * @deprecated The url parameter has been removed from the CAS server as of + * version 3.3.5.1 + */ + public static function logoutWithUrl($url) + { + trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED); + phpCAS :: traceBegin(); + if (!is_object(self::$_PHPCAS_CLIENT)) { + phpCAS :: error('this method should only be called after ' . __CLASS__ . '::client() or' . __CLASS__ . '::proxy()'); + } + if (!is_string($url)) { + phpCAS :: error('type mismatched for parameter $url (should be `string\')'); + } + self::$_PHPCAS_CLIENT->logout(array ( "url" => $url )); + // never reached + phpCAS :: traceEnd(); + } + + /** + * This method is used to logout from CAS. Halts by redirecting to the CAS + * server. + * + * @param string $service a URL that will be transmitted to the CAS server + * @param string $url a URL that will be transmitted to the CAS server + * + * @return void + * + * @deprecated The url parameter has been removed from the CAS server as of + * version 3.3.5.1 + */ + public static function logoutWithRedirectServiceAndUrl($service, $url) + { + trigger_error('Function deprecated for cas servers >= 3.3.5.1', E_USER_DEPRECATED); + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + if (!is_string($service)) { + phpCAS :: error('type mismatched for parameter $service (should be `string\')'); + } + if (!is_string($url)) { + phpCAS :: error('type mismatched for parameter $url (should be `string\')'); + } + self::$_PHPCAS_CLIENT->logout( + array ( + "service" => $service, + "url" => $url + ) + ); + // never reached + phpCAS :: traceEnd(); + } + + /** + * Set the fixed URL that will be used by the CAS server to transmit the + * PGT. When this method is not called, a phpCAS script uses its own URL + * for the callback. + * + * @param string $url the URL + * + * @return void + */ + public static function setFixedCallbackURL($url = '') + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setCallbackURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set the fixed URL that will be set as the CAS service parameter. When this + * method is not called, a phpCAS script uses its own URL. + * + * @param string $url the URL + * + * @return void + */ + public static function setFixedServiceURL($url) + { + phpCAS :: traceBegin(); + phpCAS::_validateProxyExists(); + + try { + self::$_PHPCAS_CLIENT->setURL($url); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Get the URL that is set as the CAS service parameter. + * + * @return string Service Url + */ + public static function getServiceURL() + { + phpCAS::_validateProxyExists(); + return (self::$_PHPCAS_CLIENT->getURL()); + } + + /** + * Retrieve a Proxy Ticket from the CAS server. + * + * @param string $target_service Url string of service to proxy + * @param string &$err_code error code + * @param string &$err_msg error message + * + * @return string Proxy Ticket + */ + public static function retrievePT($target_service, & $err_code, & $err_msg) + { + phpCAS::_validateProxyExists(); + + try { + return (self::$_PHPCAS_CLIENT->retrievePT($target_service, $err_code, $err_msg)); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + } + + /** + * Set the certificate of the CAS server CA and if the CN should be properly + * verified. + * + * @param string $cert CA certificate file name + * @param bool $validate_cn Validate CN in certificate (default true) + * + * @return void + */ + public static function setCasServerCACert($cert, $validate_cn = true) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->setCasServerCACert($cert, $validate_cn); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Set no SSL validation for the CAS server. + * + * @return void + */ + public static function setNoCasServerValidation() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + phpCAS :: trace('You have configured no validation of the legitimacy of the cas server. This is not recommended for production use.'); + self::$_PHPCAS_CLIENT->setNoCasServerValidation(); + phpCAS :: traceEnd(); + } + + + /** + * Disable the removal of a CAS-Ticket from the URL when authenticating + * DISABLING POSES A SECURITY RISK: + * We normally remove the ticket by an additional redirect as a security + * precaution to prevent a ticket in the HTTP_REFERRER or be carried over in + * the URL parameter + * + * @return void + */ + public static function setNoClearTicketsFromUrl() + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setNoClearTicketsFromUrl(); + phpCAS :: traceEnd(); + } + + /** @} */ + + /** + * Change CURL options. + * CURL is used to connect through HTTPS to CAS server + * + * @param string $key the option key + * @param string $value the value to set + * + * @return void + */ + public static function setExtraCurlOption($key, $value) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + self::$_PHPCAS_CLIENT->setExtraCurlOption($key, $value); + phpCAS :: traceEnd(); + } + + /** + * Set a salt/seed for the session-id hash to make it harder to guess. + * + * When $changeSessionID = true phpCAS will create a session-id that is derived + * from the service ticket. Doing so allows phpCAS to look-up and destroy the + * proper session on single-log-out requests. While the service tickets + * provided by the CAS server may include enough data to generate a strong + * hash, clients may provide an additional salt to ensure that session ids + * are not guessable if the session tickets do not have enough entropy. + * + * @param string $salt The salt to combine with the session ticket. + * + * @return void + */ + public static function setSessionIdSalt($salt) { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + self::$_PHPCAS_CLIENT->setSessionIdSalt($salt); + phpCAS :: traceEnd(); + } + + /** + * If you want your service to be proxied you have to enable it (default + * disabled) and define an accepable list of proxies that are allowed to + * proxy your service. + * + * Add each allowed proxy definition object. For the normal CAS_ProxyChain + * class, the constructor takes an array of proxies to match. The list is in + * reverse just as seen from the service. Proxies have to be defined in reverse + * from the service to the user. If a user hits service A and gets proxied via + * B to service C the list of acceptable on C would be array(B,A). The definition + * of an individual proxy can be either a string or a regexp (preg_match is used) + * that will be matched against the proxy list supplied by the cas server + * when validating the proxy tickets. The strings are compared starting from + * the beginning and must fully match with the proxies in the list. + * Example: + * phpCAS::allowProxyChain(new CAS_ProxyChain(array( + * 'https://app.example.com/' + * ))); + * phpCAS::allowProxyChain(new CAS_ProxyChain(array( + * '/^https:\/\/app[0-9]\.example\.com\/rest\//', + * 'http://client.example.com/' + * ))); + * + * For quick testing or in certain production screnarios you might want to + * allow allow any other valid service to proxy your service. To do so, add + * the "Any" chain: + * phpCAS::allowProxyChain(new CAS_ProxyChain_Any); + * THIS SETTING IS HOWEVER NOT RECOMMENDED FOR PRODUCTION AND HAS SECURITY + * IMPLICATIONS: YOU ARE ALLOWING ANY SERVICE TO ACT ON BEHALF OF A USER + * ON THIS SERVICE. + * + * @param CAS_ProxyChain_Interface $proxy_chain A proxy-chain that will be + * matched against the proxies requesting access + * + * @return void + */ + public static function allowProxyChain(CAS_ProxyChain_Interface $proxy_chain) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + if (self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_2_0 + && self::$_PHPCAS_CLIENT->getServerVersion() !== CAS_VERSION_3_0 + ) { + phpCAS :: error('this method can only be used with the cas 2.0/3.0 protocols'); + } + self::$_PHPCAS_CLIENT->getAllowedProxyChains()->allowProxyChain($proxy_chain); + phpCAS :: traceEnd(); + } + + /** + * Answer an array of proxies that are sitting in front of this application. + * This method will only return a non-empty array if we have received and + * validated a Proxy Ticket. + * + * @return array + * @access public + * @since 6/25/09 + */ + public static function getProxies () + { + phpCAS::_validateProxyExists(); + + return(self::$_PHPCAS_CLIENT->getProxies()); + } + + // ######################################################################## + // PGTIOU/PGTID and logoutRequest rebroadcasting + // ######################################################################## + + /** + * Add a pgtIou/pgtId and logoutRequest rebroadcast node. + * + * @param string $rebroadcastNodeUrl The rebroadcast node URL. Can be + * hostname or IP. + * + * @return void + */ + public static function addRebroadcastNode($rebroadcastNodeUrl) + { + phpCAS::traceBegin(); + phpCAS::log('rebroadcastNodeUrl:'.$rebroadcastNodeUrl); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->addRebroadcastNode($rebroadcastNodeUrl); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS::traceEnd(); + } + + /** + * This method is used to add header parameters when rebroadcasting + * pgtIou/pgtId or logoutRequest. + * + * @param String $header Header to send when rebroadcasting. + * + * @return void + */ + public static function addRebroadcastHeader($header) + { + phpCAS :: traceBegin(); + phpCAS::_validateClientExists(); + + try { + self::$_PHPCAS_CLIENT->addRebroadcastHeader($header); + } catch (Exception $e) { + phpCAS :: error(get_class($e) . ': ' . $e->getMessage()); + } + + phpCAS :: traceEnd(); + } + + /** + * Checks if a client already exists + * + * @throws CAS_OutOfSequenceBeforeClientException + * + * @return void + */ + private static function _validateClientExists() + { + if (!is_object(self::$_PHPCAS_CLIENT)) { + throw new CAS_OutOfSequenceBeforeClientException(); + } + } + + /** + * Checks of a proxy client aready exists + * + * @throws CAS_OutOfSequenceBeforeProxyException + * + * @return void + */ + private static function _validateProxyExists() + { + if (!is_object(self::$_PHPCAS_CLIENT)) { + throw new CAS_OutOfSequenceBeforeProxyException(); + } + } + + /** + * For testing purposes, use this method to set the client to a test double + * + * @return void + */ + public static function setCasClient(\CAS_Client $client) + { + self::$_PHPCAS_CLIENT = $client; + } +} +// ######################################################################## +// DOCUMENTATION +// ######################################################################## + +// ######################################################################## +// MAIN PAGE + +/** + * @mainpage + * + * The following pages only show the source documentation. + * + */ + +// ######################################################################## +// MODULES DEFINITION + +/** @defgroup public User interface */ + +/** @defgroup publicInit Initialization + * @ingroup public */ + +/** @defgroup publicAuth Authentication + * @ingroup public */ + +/** @defgroup publicServices Access to external services + * @ingroup public */ + +/** @defgroup publicConfig Configuration + * @ingroup public */ + +/** @defgroup publicLang Internationalization + * @ingroup publicConfig */ + +/** @defgroup publicOutput HTML output + * @ingroup publicConfig */ + +/** @defgroup publicPGTStorage PGT storage + * @ingroup publicConfig */ + +/** @defgroup publicDebug Debugging + * @ingroup public */ + +/** @defgroup internal Implementation */ + +/** @defgroup internalAuthentication Authentication + * @ingroup internal */ + +/** @defgroup internalBasic CAS Basic client features (CAS 1.0, Service Tickets) + * @ingroup internal */ + +/** @defgroup internalProxy CAS Proxy features (CAS 2.0, Proxy Granting Tickets) + * @ingroup internal */ + +/** @defgroup internalSAML CAS SAML features (SAML 1.1) + * @ingroup internal */ + +/** @defgroup internalPGTStorage PGT storage + * @ingroup internalProxy */ + +/** @defgroup internalPGTStorageDb PGT storage in a database + * @ingroup internalPGTStorage */ + +/** @defgroup internalPGTStorageFile PGT storage on the filesystem + * @ingroup internalPGTStorage */ + +/** @defgroup internalCallback Callback from the CAS server + * @ingroup internalProxy */ + +/** @defgroup internalProxyServices Proxy other services + * @ingroup internalProxy */ + +/** @defgroup internalService CAS client features (CAS 2.0, Proxied service) + * @ingroup internal */ + +/** @defgroup internalConfig Configuration + * @ingroup internal */ + +/** @defgroup internalBehave Internal behaviour of phpCAS + * @ingroup internalConfig */ + +/** @defgroup internalOutput HTML output + * @ingroup internalConfig */ + +/** @defgroup internalLang Internationalization + * @ingroup internalConfig + * + * To add a new language: + * - 1. define a new constant PHPCAS_LANG_XXXXXX in CAS/CAS.php + * - 2. copy any file from CAS/languages to CAS/languages/XXXXXX.php + * - 3. Make the translations + */ + +/** @defgroup internalDebug Debugging + * @ingroup internal */ + +/** @defgroup internalMisc Miscellaneous + * @ingroup internal */ + +// ######################################################################## +// EXAMPLES + +/** + * @example example_simple.php + */ +/** + * @example example_service.php + */ +/** + * @example example_service_that_proxies.php + */ +/** + * @example example_service_POST.php + */ +/** + * @example example_proxy_serviceWeb.php + */ +/** + * @example example_proxy_serviceWeb_chaining.php + */ +/** + * @example example_proxy_POST.php + */ +/** + * @example example_proxy_GET.php + */ +/** + * @example example_lang.php + */ +/** + * @example example_html.php + */ +/** + * @example example_pgt_storage_file.php + */ +/** + * @example example_pgt_storage_db.php + */ +/** + * @example example_gateway.php + */ +/** + * @example example_logout.php + */ +/** + * @example example_rebroadcast.php + */ +/** + * @example example_custom_urls.php + */ +/** + * @example example_advanced_saml11.php + */ +?> diff --git a/phpCas/source/CAS/AuthenticationException.php b/phpCas/source/CAS/AuthenticationException.php new file mode 100644 index 0000000..1a98d75 --- /dev/null +++ b/phpCas/source/CAS/AuthenticationException.php @@ -0,0 +1,111 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines methods that allow proxy-authenticated service handlers + * to interact with phpCAS. + * + * Proxy service handlers must implement this interface as well as call + * phpCAS::initializeProxiedService($this) at some point in their implementation. + * + * While not required, proxy-authenticated service handlers are encouraged to + * implement the CAS_ProxiedService_Testable interface to facilitate unit testing. + * + * @class CAS_AuthenticationException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_AuthenticationException +extends RuntimeException +implements CAS_Exception +{ + + /** + * This method is used to print the HTML output when the user was not + * authenticated. + * + * @param CAS_Client $client phpcas client + * @param string $failure the failure that occured + * @param string $cas_url the URL the CAS server was asked for + * @param bool $no_response the response from the CAS server (other + * parameters are ignored if TRUE) + * @param bool $bad_response bad response from the CAS server ($err_code + * and $err_msg ignored if TRUE) + * @param string $cas_response the response of the CAS server + * @param int $err_code the error code given by the CAS server + * @param string $err_msg the error message given by the CAS server + */ + public function __construct($client,$failure,$cas_url,$no_response, + $bad_response='',$cas_response='',$err_code='',$err_msg='' + ) { + $messages = array(); + phpCAS::traceBegin(); + $lang = $client->getLangObj(); + $client->printHTMLHeader($lang->getAuthenticationFailed()); + printf( + $lang->getYouWereNotAuthenticated(), + htmlentities($client->getURL()), + isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:'' + ); + phpCAS::trace($messages[] = 'CAS URL: '.$cas_url); + phpCAS::trace($messages[] = 'Authentication failure: '.$failure); + if ( $no_response ) { + phpCAS::trace($messages[] = 'Reason: no response from the CAS server'); + } else { + if ( $bad_response ) { + phpCAS::trace($messages[] = 'Reason: bad response from the CAS server'); + } else { + switch ($client->getServerVersion()) { + case CAS_VERSION_1_0: + phpCAS::trace($messages[] = 'Reason: CAS error'); + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + if ( empty($err_code) ) { + phpCAS::trace($messages[] = 'Reason: no CAS error'); + } else { + phpCAS::trace($messages[] = 'Reason: ['.$err_code.'] CAS error: '.$err_msg); + } + break; + } + } + phpCAS::trace($messages[] = 'CAS response: '.$cas_response); + } + $client->printHTMLFooter(); + phpCAS::traceExit(); + + parent::__construct(implode("\n", $messages)); + } + +} +?> diff --git a/phpCas/source/CAS/Autoload.php b/phpCas/source/CAS/Autoload.php new file mode 100644 index 0000000..5e9b140 --- /dev/null +++ b/phpCas/source/CAS/Autoload.php @@ -0,0 +1,90 @@ + + * @copyright 2008 Regents of the University of Nebraska + * @license http://www1.unl.edu/wdn/wiki/Software_License BSD License + * @link http://code.google.com/p/simplecas/ + **/ + +/** + * Autoload a class + * + * @param string $class Classname to load + * + * @return bool + */ +function CAS_autoload($class) +{ + // Static to hold the Include Path to CAS + static $include_path; + // Check only for CAS classes + if (substr($class, 0, 4) !== 'CAS_') { + return false; + } + // Setup the include path if it's not already set from a previous call + if (empty($include_path)) { + $include_path = array(dirname(dirname(__FILE__)), dirname(dirname(__FILE__)) . '/../test/' ); + } + + // Declare local variable to store the expected full path to the file + + foreach ($include_path as $path) { + $file_path = $path . '/' . str_replace('_', '/', $class) . '.php'; + $fp = @fopen($file_path, 'r', true); + if ($fp) { + fclose($fp); + include $file_path; + if (!class_exists($class, false) && !interface_exists($class, false)) { + die( + new Exception( + 'Class ' . $class . ' was not present in ' . + $file_path . + ' [CAS_autoload]' + ) + ); + } + return true; + } + } + $e = new Exception( + 'Class ' . $class . ' could not be loaded from ' . + $file_path . ', file does not exist (Path="' + . implode(':', $include_path) .'") [CAS_autoload]' + ); + $trace = $e->getTrace(); + if (isset($trace[2]) && isset($trace[2]['function']) + && in_array($trace[2]['function'], array('class_exists', 'interface_exists')) + ) { + return false; + } + if (isset($trace[1]) && isset($trace[1]['function']) + && in_array($trace[1]['function'], array('class_exists', 'interface_exists')) + ) { + return false; + } + die ((string) $e); +} + +// set up __autoload +if (!(spl_autoload_functions()) + || !in_array('CAS_autoload', spl_autoload_functions()) +) { + spl_autoload_register('CAS_autoload'); + if (function_exists('__autoload') + && !in_array('__autoload', spl_autoload_functions()) + ) { + // __autoload() was being used, but now would be ignored, add + // it to the autoload stack + spl_autoload_register('__autoload'); + } +} + +?> diff --git a/phpCas/source/CAS/Client.php b/phpCas/source/CAS/Client.php new file mode 100644 index 0000000..960e65b --- /dev/null +++ b/phpCas/source/CAS/Client.php @@ -0,0 +1,3970 @@ + + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * The CAS_Client class is a client interface that provides CAS authentication + * to PHP applications. + * + * @class CAS_Client + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @author Olivier Berger + * @author Brett Bieber + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + */ + +class CAS_Client +{ + + // ######################################################################## + // HTML OUTPUT + // ######################################################################## + /** + * @addtogroup internalOutput + * @{ + */ + + /** + * This method filters a string by replacing special tokens by appropriate values + * and prints it. The corresponding tokens are taken into account: + * - __CAS_VERSION__ + * - __PHPCAS_VERSION__ + * - __SERVER_BASE_URL__ + * + * Used by CAS_Client::PrintHTMLHeader() and CAS_Client::printHTMLFooter(). + * + * @param string $str the string to filter and output + * + * @return void + */ + private function _htmlFilterOutput($str) + { + $str = str_replace('__CAS_VERSION__', $this->getServerVersion(), $str); + $str = str_replace('__PHPCAS_VERSION__', phpCAS::getVersion(), $str); + $str = str_replace('__SERVER_BASE_URL__', $this->_getServerBaseURL(), $str); + echo $str; + } + + /** + * A string used to print the header of HTML pages. Written by + * CAS_Client::setHTMLHeader(), read by CAS_Client::printHTMLHeader(). + * + * @hideinitializer + * @see CAS_Client::setHTMLHeader, CAS_Client::printHTMLHeader() + */ + private $_output_header = ''; + + /** + * This method prints the header of the HTML output (after filtering). If + * CAS_Client::setHTMLHeader() was not used, a default header is output. + * + * @param string $title the title of the page + * + * @return void + * @see _htmlFilterOutput() + */ + public function printHTMLHeader($title) + { + $this->_htmlFilterOutput( + str_replace( + '__TITLE__', $title, + (empty($this->_output_header) + ? '__TITLE__

__TITLE__

' + : $this->_output_header) + ) + ); + } + + /** + * A string used to print the footer of HTML pages. Written by + * CAS_Client::setHTMLFooter(), read by printHTMLFooter(). + * + * @hideinitializer + * @see CAS_Client::setHTMLFooter, CAS_Client::printHTMLFooter() + */ + private $_output_footer = ''; + + /** + * This method prints the footer of the HTML output (after filtering). If + * CAS_Client::setHTMLFooter() was not used, a default footer is output. + * + * @return void + * @see _htmlFilterOutput() + */ + public function printHTMLFooter() + { + $lang = $this->getLangObj(); + $this->_htmlFilterOutput( + empty($this->_output_footer)? + (phpCAS::getVerbose())? + '
phpCAS __PHPCAS_VERSION__ ' + .$lang->getUsingServer() + .' __SERVER_BASE_URL__ (CAS __CAS_VERSION__)
' + :'' + :$this->_output_footer + ); + } + + /** + * This method set the HTML header used for all outputs. + * + * @param string $header the HTML header. + * + * @return void + */ + public function setHTMLHeader($header) + { + // Argument Validation + if (gettype($header) != 'string') + throw new CAS_TypeMismatchException($header, '$header', 'string'); + + $this->_output_header = $header; + } + + /** + * This method set the HTML footer used for all outputs. + * + * @param string $footer the HTML footer. + * + * @return void + */ + public function setHTMLFooter($footer) + { + // Argument Validation + if (gettype($footer) != 'string') + throw new CAS_TypeMismatchException($footer, '$footer', 'string'); + + $this->_output_footer = $footer; + } + + + /** @} */ + + + // ######################################################################## + // INTERNATIONALIZATION + // ######################################################################## + /** + * @addtogroup internalLang + * @{ + */ + /** + * A string corresponding to the language used by phpCAS. Written by + * CAS_Client::setLang(), read by CAS_Client::getLang(). + + * @note debugging information is always in english (debug purposes only). + */ + private $_lang = PHPCAS_LANG_DEFAULT; + + /** + * This method is used to set the language used by phpCAS. + * + * @param string $lang representing the language. + * + * @return void + */ + public function setLang($lang) + { + // Argument Validation + if (gettype($lang) != 'string') + throw new CAS_TypeMismatchException($lang, '$lang', 'string'); + + phpCAS::traceBegin(); + $obj = new $lang(); + if (!($obj instanceof CAS_Languages_LanguageInterface)) { + throw new CAS_InvalidArgumentException( + '$className must implement the CAS_Languages_LanguageInterface' + ); + } + $this->_lang = $lang; + phpCAS::traceEnd(); + } + /** + * Create the language + * + * @return CAS_Languages_LanguageInterface object implementing the class + */ + public function getLangObj() + { + $classname = $this->_lang; + return new $classname(); + } + + /** @} */ + // ######################################################################## + // CAS SERVER CONFIG + // ######################################################################## + /** + * @addtogroup internalConfig + * @{ + */ + + /** + * a record to store information about the CAS server. + * - $_server['version']: the version of the CAS server + * - $_server['hostname']: the hostname of the CAS server + * - $_server['port']: the port the CAS server is running on + * - $_server['uri']: the base URI the CAS server is responding on + * - $_server['base_url']: the base URL of the CAS server + * - $_server['login_url']: the login URL of the CAS server + * - $_server['service_validate_url']: the service validating URL of the + * CAS server + * - $_server['proxy_url']: the proxy URL of the CAS server + * - $_server['proxy_validate_url']: the proxy validating URL of the CAS server + * - $_server['logout_url']: the logout URL of the CAS server + * + * $_server['version'], $_server['hostname'], $_server['port'] and + * $_server['uri'] are written by CAS_Client::CAS_Client(), read by + * CAS_Client::getServerVersion(), CAS_Client::_getServerHostname(), + * CAS_Client::_getServerPort() and CAS_Client::_getServerURI(). + * + * The other fields are written and read by CAS_Client::_getServerBaseURL(), + * CAS_Client::getServerLoginURL(), CAS_Client::getServerServiceValidateURL(), + * CAS_Client::getServerProxyValidateURL() and CAS_Client::getServerLogoutURL(). + * + * @hideinitializer + */ + private $_server = array( + 'version' => -1, + 'hostname' => 'none', + 'port' => -1, + 'uri' => 'none'); + + /** + * This method is used to retrieve the version of the CAS server. + * + * @return string the version of the CAS server. + */ + public function getServerVersion() + { + return $this->_server['version']; + } + + /** + * This method is used to retrieve the hostname of the CAS server. + * + * @return string the hostname of the CAS server. + */ + private function _getServerHostname() + { + return $this->_server['hostname']; + } + + /** + * This method is used to retrieve the port of the CAS server. + * + * @return string the port of the CAS server. + */ + private function _getServerPort() + { + return $this->_server['port']; + } + + /** + * This method is used to retrieve the URI of the CAS server. + * + * @return string a URI. + */ + private function _getServerURI() + { + return $this->_server['uri']; + } + + /** + * This method is used to retrieve the base URL of the CAS server. + * + * @return string a URL. + */ + private function _getServerBaseURL() + { + // the URL is build only when needed + if ( empty($this->_server['base_url']) ) { + $this->_server['base_url'] = 'https://' . $this->_getServerHostname(); + if ($this->_getServerPort()!=443) { + $this->_server['base_url'] .= ':' + .$this->_getServerPort(); + } + $this->_server['base_url'] .= $this->_getServerURI(); + } + return $this->_server['base_url']; + } + + /** + * This method is used to retrieve the login URL of the CAS server. + * + * @param bool $gateway true to check authentication, false to force it + * @param bool $renew true to force the authentication with the CAS server + * + * @return a URL. + * @note It is recommended that CAS implementations ignore the "gateway" + * parameter if "renew" is set + */ + public function getServerLoginURL($gateway=false,$renew=false) + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['login_url']) ) { + $this->_server['login_url'] = $this->_buildQueryUrl($this->_getServerBaseURL().'login','service='.urlencode($this->getURL())); + } + $url = $this->_server['login_url']; + if ($renew) { + // It is recommended that when the "renew" parameter is set, its + // value be "true" + $url = $this->_buildQueryUrl($url, 'renew=true'); + } elseif ($gateway) { + // It is recommended that when the "gateway" parameter is set, its + // value be "true" + $url = $this->_buildQueryUrl($url, 'gateway=true'); + } + phpCAS::traceEnd($url); + return $url; + } + + /** + * This method sets the login URL of the CAS server. + * + * @param string $url the login URL + * + * @return string login url + */ + public function setServerLoginURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['login_url'] = $url; + } + + + /** + * This method sets the serviceValidate URL of the CAS server. + * + * @param string $url the serviceValidate URL + * + * @return string serviceValidate URL + */ + public function setServerServiceValidateURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['service_validate_url'] = $url; + } + + + /** + * This method sets the proxyValidate URL of the CAS server. + * + * @param string $url the proxyValidate URL + * + * @return string proxyValidate URL + */ + public function setServerProxyValidateURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['proxy_validate_url'] = $url; + } + + + /** + * This method sets the samlValidate URL of the CAS server. + * + * @param string $url the samlValidate URL + * + * @return string samlValidate URL + */ + public function setServerSamlValidateURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['saml_validate_url'] = $url; + } + + + /** + * This method is used to retrieve the service validating URL of the CAS server. + * + * @return string serviceValidate URL. + */ + public function getServerServiceValidateURL() + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['service_validate_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'validate'; + break; + case CAS_VERSION_2_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'serviceValidate'; + break; + case CAS_VERSION_3_0: + $this->_server['service_validate_url'] = $this->_getServerBaseURL() + .'p3/serviceValidate'; + break; + } + } + $url = $this->_buildQueryUrl( + $this->_server['service_validate_url'], + 'service='.urlencode($this->getURL()) + ); + phpCAS::traceEnd($url); + return $url; + } + /** + * This method is used to retrieve the SAML validating URL of the CAS server. + * + * @return string samlValidate URL. + */ + public function getServerSamlValidateURL() + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['saml_validate_url']) ) { + switch ($this->getServerVersion()) { + case SAML_VERSION_1_1: + $this->_server['saml_validate_url'] = $this->_getServerBaseURL().'samlValidate'; + break; + } + } + + $url = $this->_buildQueryUrl( + $this->_server['saml_validate_url'], + 'TARGET='.urlencode($this->getURL()) + ); + phpCAS::traceEnd($url); + return $url; + } + + /** + * This method is used to retrieve the proxy validating URL of the CAS server. + * + * @return string proxyValidate URL. + */ + public function getServerProxyValidateURL() + { + phpCAS::traceBegin(); + // the URL is build only when needed + if ( empty($this->_server['proxy_validate_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['proxy_validate_url'] = ''; + break; + case CAS_VERSION_2_0: + $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate'; + break; + case CAS_VERSION_3_0: + $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'p3/proxyValidate'; + break; + } + } + $url = $this->_buildQueryUrl( + $this->_server['proxy_validate_url'], + 'service='.urlencode($this->getURL()) + ); + phpCAS::traceEnd($url); + return $url; + } + + + /** + * This method is used to retrieve the proxy URL of the CAS server. + * + * @return string proxy URL. + */ + public function getServerProxyURL() + { + // the URL is build only when needed + if ( empty($this->_server['proxy_url']) ) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + $this->_server['proxy_url'] = ''; + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy'; + break; + } + } + return $this->_server['proxy_url']; + } + + /** + * This method is used to retrieve the logout URL of the CAS server. + * + * @return string logout URL. + */ + public function getServerLogoutURL() + { + // the URL is build only when needed + if ( empty($this->_server['logout_url']) ) { + $this->_server['logout_url'] = $this->_getServerBaseURL().'logout'; + } + return $this->_server['logout_url']; + } + + /** + * This method sets the logout URL of the CAS server. + * + * @param string $url the logout URL + * + * @return string logout url + */ + public function setServerLogoutURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['logout_url'] = $url; + } + + /** + * An array to store extra curl options. + */ + private $_curl_options = array(); + + /** + * This method is used to set additional user curl options. + * + * @param string $key name of the curl option + * @param string $value value of the curl option + * + * @return void + */ + public function setExtraCurlOption($key, $value) + { + $this->_curl_options[$key] = $value; + } + + /** @} */ + + // ######################################################################## + // Change the internal behaviour of phpcas + // ######################################################################## + + /** + * @addtogroup internalBehave + * @{ + */ + + /** + * The class to instantiate for making web requests in readUrl(). + * The class specified must implement the CAS_Request_RequestInterface. + * By default CAS_Request_CurlRequest is used, but this may be overridden to + * supply alternate request mechanisms for testing. + */ + private $_requestImplementation = 'CAS_Request_CurlRequest'; + + /** + * Override the default implementation used to make web requests in readUrl(). + * This class must implement the CAS_Request_RequestInterface. + * + * @param string $className name of the RequestImplementation class + * + * @return void + */ + public function setRequestImplementation ($className) + { + $obj = new $className; + if (!($obj instanceof CAS_Request_RequestInterface)) { + throw new CAS_InvalidArgumentException( + '$className must implement the CAS_Request_RequestInterface' + ); + } + $this->_requestImplementation = $className; + } + + /** + * @var boolean $_clearTicketsFromUrl; If true, phpCAS will clear session + * tickets from the URL after a successful authentication. + */ + private $_clearTicketsFromUrl = true; + + /** + * Configure the client to not send redirect headers and call exit() on + * authentication success. The normal redirect is used to remove the service + * ticket from the client's URL, but for running unit tests we need to + * continue without exiting. + * + * Needed for testing authentication + * + * @return void + */ + public function setNoClearTicketsFromUrl () + { + $this->_clearTicketsFromUrl = false; + } + + /** + * @var callback $_attributeParserCallbackFunction; + */ + private $_casAttributeParserCallbackFunction = null; + + /** + * @var array $_attributeParserCallbackArgs; + */ + private $_casAttributeParserCallbackArgs = array(); + + /** + * Set a callback function to be run when parsing CAS attributes + * + * The callback function will be passed a XMLNode as its first parameter, + * followed by any $additionalArgs you pass. + * + * @param string $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setCasAttributeParserCallback($function, array $additionalArgs = array()) + { + $this->_casAttributeParserCallbackFunction = $function; + $this->_casAttributeParserCallbackArgs = $additionalArgs; + } + + /** @var callback $_postAuthenticateCallbackFunction; + */ + private $_postAuthenticateCallbackFunction = null; + + /** + * @var array $_postAuthenticateCallbackArgs; + */ + private $_postAuthenticateCallbackArgs = array(); + + /** + * Set a callback function to be run when a user authenticates. + * + * The callback function will be passed a $logoutTicket as its first parameter, + * followed by any $additionalArgs you pass. The $logoutTicket parameter is an + * opaque string that can be used to map a session-id to the logout request + * in order to support single-signout in applications that manage their own + * sessions (rather than letting phpCAS start the session). + * + * phpCAS::forceAuthentication() will always exit and forward client unless + * they are already authenticated. To perform an action at the moment the user + * logs in (such as registering an account, performing logging, etc), register + * a callback function here. + * + * @param string $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setPostAuthenticateCallback ($function, array $additionalArgs = array()) + { + $this->_postAuthenticateCallbackFunction = $function; + $this->_postAuthenticateCallbackArgs = $additionalArgs; + } + + /** + * @var callback $_signoutCallbackFunction; + */ + private $_signoutCallbackFunction = null; + + /** + * @var array $_signoutCallbackArgs; + */ + private $_signoutCallbackArgs = array(); + + /** + * Set a callback function to be run when a single-signout request is received. + * + * The callback function will be passed a $logoutTicket as its first parameter, + * followed by any $additionalArgs you pass. The $logoutTicket parameter is an + * opaque string that can be used to map a session-id to the logout request in + * order to support single-signout in applications that manage their own sessions + * (rather than letting phpCAS start and destroy the session). + * + * @param string $function callback function to call + * @param array $additionalArgs optional array of arguments + * + * @return void + */ + public function setSingleSignoutCallback ($function, array $additionalArgs = array()) + { + $this->_signoutCallbackFunction = $function; + $this->_signoutCallbackArgs = $additionalArgs; + } + + // ######################################################################## + // Methods for supplying code-flow feedback to integrators. + // ######################################################################## + + /** + * Ensure that this is actually a proxy object or fail with an exception + * + * @throws CAS_OutOfSequenceBeforeProxyException + * + * @return void + */ + public function ensureIsProxy() + { + if (!$this->isProxy()) { + throw new CAS_OutOfSequenceBeforeProxyException(); + } + } + + /** + * Mark the caller of authentication. This will help client integraters determine + * problems with their code flow if they call a function such as getUser() before + * authentication has occurred. + * + * @param bool $auth True if authentication was successful, false otherwise. + * + * @return null + */ + public function markAuthenticationCall ($auth) + { + // store where the authentication has been checked and the result + $dbg = debug_backtrace(); + $this->_authentication_caller = array ( + 'file' => $dbg[1]['file'], + 'line' => $dbg[1]['line'], + 'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'], + 'result' => (boolean)$auth + ); + } + private $_authentication_caller; + + /** + * Answer true if authentication has been checked. + * + * @return bool + */ + public function wasAuthenticationCalled () + { + return !empty($this->_authentication_caller); + } + + /** + * Ensure that authentication was checked. Terminate with exception if no + * authentication was performed + * + * @throws CAS_OutOfSequenceBeforeAuthenticationCallException + * + * @return void + */ + private function _ensureAuthenticationCalled() + { + if (!$this->wasAuthenticationCalled()) { + throw new CAS_OutOfSequenceBeforeAuthenticationCallException(); + } + } + + /** + * Answer the result of the authentication call. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return bool + */ + public function wasAuthenticationCallSuccessful () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['result']; + } + + + /** + * Ensure that authentication was checked. Terminate with exception if no + * authentication was performed + * + * @throws CAS_OutOfSequenceBeforeAuthenticationCallException + * + * @return void + */ + public function ensureAuthenticationCallSuccessful() + { + $this->_ensureAuthenticationCalled(); + if (!$this->_authentication_caller['result']) { + throw new CAS_OutOfSequenceException( + 'authentication was checked (by ' + . $this->getAuthenticationCallerMethod() + . '() at ' . $this->getAuthenticationCallerFile() + . ':' . $this->getAuthenticationCallerLine() + . ') but the method returned false' + ); + } + } + + /** + * Answer information about the authentication caller. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return array Keys are 'file', 'line', and 'method' + */ + public function getAuthenticationCallerFile () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['file']; + } + + /** + * Answer information about the authentication caller. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return array Keys are 'file', 'line', and 'method' + */ + public function getAuthenticationCallerLine () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['line']; + } + + /** + * Answer information about the authentication caller. + * + * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false + * and markAuthenticationCall() didn't happen. + * + * @return array Keys are 'file', 'line', and 'method' + */ + public function getAuthenticationCallerMethod () + { + $this->_ensureAuthenticationCalled(); + return $this->_authentication_caller['method']; + } + + /** @} */ + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + /** + * @addtogroup internalConfig + * @{ + */ + + /** + * CAS_Client constructor. + * + * @param string $server_version the version of the CAS server + * @param bool $proxy true if the CAS client is a CAS proxy + * @param string $server_hostname the hostname of the CAS server + * @param int $server_port the port the CAS server is running on + * @param string $server_uri the URI the CAS server is responding on + * @param bool $changeSessionID Allow phpCAS to change the session_id + * (Single Sign Out/handleLogoutRequests + * is based on that change) + * + * @return a newly created CAS_Client object + */ + public function __construct( + $server_version, + $proxy, + $server_hostname, + $server_port, + $server_uri, + $changeSessionID = true + ) { + // Argument validation + if (gettype($server_version) != 'string') + throw new CAS_TypeMismatchException($server_version, '$server_version', 'string'); + if (gettype($proxy) != 'boolean') + throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean'); + if (gettype($server_hostname) != 'string') + throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string'); + if (gettype($server_port) != 'integer') + throw new CAS_TypeMismatchException($server_port, '$server_port', 'integer'); + if (gettype($server_uri) != 'string') + throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string'); + if (gettype($changeSessionID) != 'boolean') + throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean'); + + phpCAS::traceBegin(); + // true : allow to change the session_id(), false session_id won't be + // change and logout won't be handle because of that + $this->_setChangeSessionID($changeSessionID); + + // skip Session Handling for logout requests and if don't want it' + if (session_id()=="" && !$this->_isLogoutRequest()) { + session_start(); + phpCAS :: trace("Starting a new session " . session_id()); + } + // Only for debug purposes + if ($this->isSessionAuthenticated()){ + phpCAS :: trace("Session is authenticated as: " . $_SESSION['phpCAS']['user']); + } else { + phpCAS :: trace("Session is not authenticated"); + } + // are we in proxy mode ? + $this->_proxy = $proxy; + + // Make cookie handling available. + if ($this->isProxy()) { + if (!isset($_SESSION['phpCAS'])) { + $_SESSION['phpCAS'] = array(); + } + if (!isset($_SESSION['phpCAS']['service_cookies'])) { + $_SESSION['phpCAS']['service_cookies'] = array(); + } + $this->_serviceCookieJar = new CAS_CookieJar( + $_SESSION['phpCAS']['service_cookies'] + ); + } + + //check version + switch ($server_version) { + case CAS_VERSION_1_0: + if ( $this->isProxy() ) { + phpCAS::error( + 'CAS proxies are not supported in CAS '.$server_version + ); + } + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + break; + case SAML_VERSION_1_1: + break; + default: + phpCAS::error( + 'this version of CAS (`'.$server_version + .'\') is not supported by phpCAS '.phpCAS::getVersion() + ); + } + $this->_server['version'] = $server_version; + + // check hostname + if ( empty($server_hostname) + || !preg_match('/[\.\d\-abcdefghijklmnopqrstuvwxyz]*/', $server_hostname) + ) { + phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')'); + } + $this->_server['hostname'] = $server_hostname; + + // check port + if ( $server_port == 0 + || !is_int($server_port) + ) { + phpCAS::error('bad CAS server port (`'.$server_hostname.'\')'); + } + $this->_server['port'] = $server_port; + + // check URI + if ( !preg_match('/[\.\d\-_abcdefghijklmnopqrstuvwxyz\/]*/', $server_uri) ) { + phpCAS::error('bad CAS server URI (`'.$server_uri.'\')'); + } + // add leading and trailing `/' and remove doubles + if(strstr($server_uri, '?') === false) $server_uri .= '/'; + $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri); + $this->_server['uri'] = $server_uri; + + // set to callback mode if PgtIou and PgtId CGI GET parameters are provided + if ( $this->isProxy() ) { + $this->_setCallbackMode(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId'])); + } + + if ( $this->_isCallbackMode() ) { + //callback mode: check that phpCAS is secured + if ( !$this->_isHttps() ) { + phpCAS::error( + 'CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server' + ); + } + } else { + //normal mode: get ticket and remove it from CGI parameters for + // developers + $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : null); + if (preg_match('/^[SP]T-/', $ticket) ) { + phpCAS::trace('Ticket \''.$ticket.'\' found'); + $this->setTicket($ticket); + unset($_GET['ticket']); + } else if ( !empty($ticket) ) { + //ill-formed ticket, halt + phpCAS::error( + 'ill-formed ticket found in the URL (ticket=`' + .htmlentities($ticket).'\')' + ); + } + + } + phpCAS::traceEnd(); + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX Session Handling XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalConfig + * @{ + */ + + + /** + * A variable to whether phpcas will use its own session handling. Default = true + * @hideinitializer + */ + private $_change_session_id = true; + + /** + * Set a parameter whether to allow phpCas to change session_id + * + * @param bool $allowed allow phpCas to change session_id + * + * @return void + */ + private function _setChangeSessionID($allowed) + { + $this->_change_session_id = $allowed; + } + + /** + * Get whether phpCas is allowed to change session_id + * + * @return bool + */ + public function getChangeSessionID() + { + return $this->_change_session_id; + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX AUTHENTICATION XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalAuthentication + * @{ + */ + + /** + * The Authenticated user. Written by CAS_Client::_setUser(), read by + * CAS_Client::getUser(). + * + * @hideinitializer + */ + private $_user = ''; + + /** + * This method sets the CAS user's login name. + * + * @param string $user the login name of the authenticated user. + * + * @return void + */ + private function _setUser($user) + { + $this->_user = $user; + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * + * @warning should be called only after CAS_Client::forceAuthentication() or + * CAS_Client::isAuthenticated(), otherwise halt with an error. + */ + public function getUser() + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return $this->_getUser(); + } + + /** + * This method returns the CAS user's login name. + * + * @return string the login name of the authenticated user + * + * @warning should be called only after CAS_Client::forceAuthentication() or + * CAS_Client::isAuthenticated(), otherwise halt with an error. + */ + private function _getUser() + { + // This is likely a duplicate check that could be removed.... + if ( empty($this->_user) ) { + phpCAS::error( + 'this method should be used only after '.__CLASS__ + .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()' + ); + } + return $this->_user; + } + + /** + * The Authenticated users attributes. Written by + * CAS_Client::setAttributes(), read by CAS_Client::getAttributes(). + * @attention client applications should use phpCAS::getAttributes(). + * + * @hideinitializer + */ + private $_attributes = array(); + + /** + * Set an array of attributes + * + * @param array $attributes a key value array of attributes + * + * @return void + */ + public function setAttributes($attributes) + { + $this->_attributes = $attributes; + } + + /** + * Get an key values arry of attributes + * + * @return arry of attributes + */ + public function getAttributes() + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + // This is likely a duplicate check that could be removed.... + if ( empty($this->_user) ) { + // if no user is set, there shouldn't be any attributes also... + phpCAS::error( + 'this method should be used only after '.__CLASS__ + .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()' + ); + } + return $this->_attributes; + } + + /** + * Check whether attributes are available + * + * @return bool attributes available + */ + public function hasAttributes() + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return !empty($this->_attributes); + } + /** + * Check whether a specific attribute with a name is available + * + * @param string $key name of attribute + * + * @return bool is attribute available + */ + public function hasAttribute($key) + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + return $this->_hasAttribute($key); + } + + /** + * Check whether a specific attribute with a name is available + * + * @param string $key name of attribute + * + * @return bool is attribute available + */ + private function _hasAttribute($key) + { + return (is_array($this->_attributes) + && array_key_exists($key, $this->_attributes)); + } + + /** + * Get a specific attribute by name + * + * @param string $key name of attribute + * + * @return string attribute values + */ + public function getAttribute($key) + { + // Sequence validation + $this->ensureAuthenticationCallSuccessful(); + + if ($this->_hasAttribute($key)) { + return $this->_attributes[$key]; + } + } + + /** + * This method is called to renew the authentication of the user + * If the user is authenticated, renew the connection + * If not, redirect to CAS + * + * @return true when the user is authenticated; otherwise halt. + */ + public function renewAuthentication() + { + phpCAS::traceBegin(); + // Either way, the user is authenticated by CAS + if (isset( $_SESSION['phpCAS']['auth_checked'])) { + unset($_SESSION['phpCAS']['auth_checked']); + } + if ( $this->isAuthenticated(true) ) { + phpCAS::trace('user already authenticated'); + $res = true; + } else { + $this->redirectToCas(false, true); + // never reached + $res = false; + } + phpCAS::traceEnd(); + return $res; + } + + /** + * This method is called to be sure that the user is authenticated. When not + * authenticated, halt by redirecting to the CAS server; otherwise return true. + * + * @return true when the user is authenticated; otherwise halt. + */ + public function forceAuthentication() + { + phpCAS::traceBegin(); + + if ( $this->isAuthenticated() ) { + // the user is authenticated, nothing to be done. + phpCAS::trace('no need to authenticate'); + $res = true; + } else { + // the user is not authenticated, redirect to the CAS server + if (isset($_SESSION['phpCAS']['auth_checked'])) { + unset($_SESSION['phpCAS']['auth_checked']); + } + $this->redirectToCas(false/* no gateway */); + // never reached + $res = false; + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * An integer that gives the number of times authentication will be cached + * before rechecked. + * + * @hideinitializer + */ + private $_cache_times_for_auth_recheck = 0; + + /** + * Set the number of times authentication will be cached before rechecked. + * + * @param int $n number of times to wait for a recheck + * + * @return void + */ + public function setCacheTimesForAuthRecheck($n) + { + if (gettype($n) != 'integer') + throw new CAS_TypeMismatchException($n, '$n', 'string'); + + $this->_cache_times_for_auth_recheck = $n; + } + + /** + * This method is called to check whether the user is authenticated or not. + * + * @return true when the user is authenticated, false when a previous + * gateway login failed or the function will not return if the user is + * redirected to the cas server for a gateway login attempt + */ + public function checkAuthentication() + { + phpCAS::traceBegin(); + $res = false; + if ( $this->isAuthenticated() ) { + phpCAS::trace('user is authenticated'); + /* The 'auth_checked' variable is removed just in case it's set. */ + unset($_SESSION['phpCAS']['auth_checked']); + $res = true; + } else if (isset($_SESSION['phpCAS']['auth_checked'])) { + // the previous request has redirected the client to the CAS server + // with gateway=true + unset($_SESSION['phpCAS']['auth_checked']); + $res = false; + } else { + // avoid a check against CAS on every request + if (!isset($_SESSION['phpCAS']['unauth_count'])) { + $_SESSION['phpCAS']['unauth_count'] = -2; // uninitialized + } + + if (($_SESSION['phpCAS']['unauth_count'] != -2 + && $this->_cache_times_for_auth_recheck == -1) + || ($_SESSION['phpCAS']['unauth_count'] >= 0 + && $_SESSION['phpCAS']['unauth_count'] < $this->_cache_times_for_auth_recheck) + ) { + $res = false; + + if ($this->_cache_times_for_auth_recheck != -1) { + $_SESSION['phpCAS']['unauth_count']++; + phpCAS::trace( + 'user is not authenticated (cached for ' + .$_SESSION['phpCAS']['unauth_count'].' times of ' + .$this->_cache_times_for_auth_recheck.')' + ); + } else { + phpCAS::trace( + 'user is not authenticated (cached for until login pressed)' + ); + } + } else { + $_SESSION['phpCAS']['unauth_count'] = 0; + $_SESSION['phpCAS']['auth_checked'] = true; + phpCAS::trace('user is not authenticated (cache reset)'); + $this->redirectToCas(true/* gateway */); + // never reached + $res = false; + } + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method is called to check if the user is authenticated (previously or by + * tickets given in the URL). + * + * @param bool $renew true to force the authentication with the CAS server + * + * @return true when the user is authenticated. Also may redirect to the + * same URL without the ticket. + */ + public function isAuthenticated($renew=false) + { + phpCAS::traceBegin(); + $res = false; + $validate_url = ''; + if ( $this->_wasPreviouslyAuthenticated() ) { + if ($this->hasTicket()) { + // User has a additional ticket but was already authenticated + phpCAS::trace( + 'ticket was present and will be discarded, use renewAuthenticate()' + ); + if ($this->_clearTicketsFromUrl) { + phpCAS::trace("Prepare redirect to : ".$this->getURL()); + session_write_close(); + header('Location: '.$this->getURL()); + flush(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } else { + phpCAS::trace( + 'Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.' + ); + $res = true; + } + } else { + // the user has already (previously during the session) been + // authenticated, nothing to be done. + phpCAS::trace( + 'user was already authenticated, no need to look for tickets' + ); + $res = true; + } + + // Mark the auth-check as complete to allow post-authentication + // callbacks to make use of phpCAS::getUser() and similar methods + $this->markAuthenticationCall($res); + } else { + if ($this->hasTicket()) { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + // if a Service Ticket was given, validate it + phpCAS::trace( + 'CAS 1.0 ticket `'.$this->getTicket().'\' is present' + ); + $this->validateCAS10( + $validate_url, $text_response, $tree_response, $renew + ); // if it fails, it halts + phpCAS::trace( + 'CAS 1.0 ticket `'.$this->getTicket().'\' was validated' + ); + $_SESSION['phpCAS']['user'] = $this->_getUser(); + $res = true; + $logoutTicket = $this->getTicket(); + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + // if a Proxy Ticket was given, validate it + phpCAS::trace( + 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' is present' + ); + $this->validateCAS20( + $validate_url, $text_response, $tree_response, $renew + ); // note: if it fails, it halts + phpCAS::trace( + 'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' was validated' + ); + if ( $this->isProxy() ) { + $this->_validatePGT( + $validate_url, $text_response, $tree_response + ); // idem + phpCAS::trace('PGT `'.$this->_getPGT().'\' was validated'); + $_SESSION['phpCAS']['pgt'] = $this->_getPGT(); + } + $_SESSION['phpCAS']['user'] = $this->_getUser(); + if (!empty($this->_attributes)) { + $_SESSION['phpCAS']['attributes'] = $this->_attributes; + } + $proxies = $this->getProxies(); + if (!empty($proxies)) { + $_SESSION['phpCAS']['proxies'] = $this->getProxies(); + } + $res = true; + $logoutTicket = $this->getTicket(); + break; + case SAML_VERSION_1_1: + // if we have a SAML ticket, validate it. + phpCAS::trace( + 'SAML 1.1 ticket `'.$this->getTicket().'\' is present' + ); + $this->validateSA( + $validate_url, $text_response, $tree_response, $renew + ); // if it fails, it halts + phpCAS::trace( + 'SAML 1.1 ticket `'.$this->getTicket().'\' was validated' + ); + $_SESSION['phpCAS']['user'] = $this->_getUser(); + $_SESSION['phpCAS']['attributes'] = $this->_attributes; + $res = true; + $logoutTicket = $this->getTicket(); + break; + default: + phpCAS::trace('Protocoll error'); + break; + } + } else { + // no ticket given, not authenticated + phpCAS::trace('no ticket found'); + } + + // Mark the auth-check as complete to allow post-authentication + // callbacks to make use of phpCAS::getUser() and similar methods + $this->markAuthenticationCall($res); + + if ($res) { + // call the post-authenticate callback if registered. + if ($this->_postAuthenticateCallbackFunction) { + $args = $this->_postAuthenticateCallbackArgs; + array_unshift($args, $logoutTicket); + call_user_func_array( + $this->_postAuthenticateCallbackFunction, $args + ); + } + + // if called with a ticket parameter, we need to redirect to the + // app without the ticket so that CAS-ification is transparent + // to the browser (for later POSTS) most of the checks and + // errors should have been made now, so we're safe for redirect + // without masking error messages. remove the ticket as a + // security precaution to prevent a ticket in the HTTP_REFERRER + if ($this->_clearTicketsFromUrl) { + phpCAS::trace("Prepare redirect to : ".$this->getURL()); + session_write_close(); + header('Location: '.$this->getURL()); + flush(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + } + } + phpCAS::traceEnd($res); + return $res; + } + + /** + * This method tells if the current session is authenticated. + * + * @return true if authenticated based soley on $_SESSION variable + */ + public function isSessionAuthenticated () + { + return !empty($_SESSION['phpCAS']['user']); + } + + /** + * This method tells if the user has already been (previously) authenticated + * by looking into the session variables. + * + * @note This function switches to callback mode when needed. + * + * @return true when the user has already been authenticated; false otherwise. + */ + private function _wasPreviouslyAuthenticated() + { + phpCAS::traceBegin(); + + if ( $this->_isCallbackMode() ) { + // Rebroadcast the pgtIou and pgtId to all nodes + if ($this->_rebroadcast&&!isset($_POST['rebroadcast'])) { + $this->_rebroadcast(self::PGTIOU); + } + $this->_callback(); + } + + $auth = false; + + if ( $this->isProxy() ) { + // CAS proxy: username and PGT must be present + if ( $this->isSessionAuthenticated() + && !empty($_SESSION['phpCAS']['pgt']) + ) { + // authentication already done + $this->_setUser($_SESSION['phpCAS']['user']); + if (isset($_SESSION['phpCAS']['attributes'])) { + $this->setAttributes($_SESSION['phpCAS']['attributes']); + } + $this->_setPGT($_SESSION['phpCAS']['pgt']); + phpCAS::trace( + 'user = `'.$_SESSION['phpCAS']['user'].'\', PGT = `' + .$_SESSION['phpCAS']['pgt'].'\'' + ); + + // Include the list of proxies + if (isset($_SESSION['phpCAS']['proxies'])) { + $this->_setProxies($_SESSION['phpCAS']['proxies']); + phpCAS::trace( + 'proxies = "' + .implode('", "', $_SESSION['phpCAS']['proxies']).'"' + ); + } + + $auth = true; + } elseif ( $this->isSessionAuthenticated() + && empty($_SESSION['phpCAS']['pgt']) + ) { + // these two variables should be empty or not empty at the same time + phpCAS::trace( + 'username found (`'.$_SESSION['phpCAS']['user'] + .'\') but PGT is empty' + ); + // unset all tickets to enforce authentication + unset($_SESSION['phpCAS']); + $this->setTicket(''); + } elseif ( !$this->isSessionAuthenticated() + && !empty($_SESSION['phpCAS']['pgt']) + ) { + // these two variables should be empty or not empty at the same time + phpCAS::trace( + 'PGT found (`'.$_SESSION['phpCAS']['pgt'] + .'\') but username is empty' + ); + // unset all tickets to enforce authentication + unset($_SESSION['phpCAS']); + $this->setTicket(''); + } else { + phpCAS::trace('neither user nor PGT found'); + } + } else { + // `simple' CAS client (not a proxy): username must be present + if ( $this->isSessionAuthenticated() ) { + // authentication already done + $this->_setUser($_SESSION['phpCAS']['user']); + if (isset($_SESSION['phpCAS']['attributes'])) { + $this->setAttributes($_SESSION['phpCAS']['attributes']); + } + phpCAS::trace('user = `'.$_SESSION['phpCAS']['user'].'\''); + + // Include the list of proxies + if (isset($_SESSION['phpCAS']['proxies'])) { + $this->_setProxies($_SESSION['phpCAS']['proxies']); + phpCAS::trace( + 'proxies = "' + .implode('", "', $_SESSION['phpCAS']['proxies']).'"' + ); + } + + $auth = true; + } else { + phpCAS::trace('no user found'); + } + } + + phpCAS::traceEnd($auth); + return $auth; + } + + /** + * This method is used to redirect the client to the CAS server. + * It is used by CAS_Client::forceAuthentication() and + * CAS_Client::checkAuthentication(). + * + * @param bool $gateway true to check authentication, false to force it + * @param bool $renew true to force the authentication with the CAS server + * + * @return void + */ + public function redirectToCas($gateway=false,$renew=false) + { + phpCAS::traceBegin(); + $cas_url = $this->getServerLoginURL($gateway, $renew); + session_write_close(); + if (php_sapi_name() === 'cli') { + @header('Location: '.$cas_url); + } else { + header('Location: '.$cas_url); + } + phpCAS::trace("Redirect to : ".$cas_url); + $lang = $this->getLangObj(); + $this->printHTMLHeader($lang->getAuthenticationWanted()); + printf('

'. $lang->getShouldHaveBeenRedirected(). '

', $cas_url); + $this->printHTMLFooter(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + + + /** + * This method is used to logout from CAS. + * + * @param array $params an array that contains the optional url and service + * parameters that will be passed to the CAS server + * + * @return void + */ + public function logout($params) + { + phpCAS::traceBegin(); + $cas_url = $this->getServerLogoutURL(); + $paramSeparator = '?'; + if (isset($params['url'])) { + $cas_url = $cas_url . $paramSeparator . "url=" + . urlencode($params['url']); + $paramSeparator = '&'; + } + if (isset($params['service'])) { + $cas_url = $cas_url . $paramSeparator . "service=" + . urlencode($params['service']); + } + header('Location: '.$cas_url); + phpCAS::trace("Prepare redirect to : ".$cas_url); + + phpCAS::trace("Destroying session : ".session_id()); + session_unset(); + session_destroy(); + if (session_status() === PHP_SESSION_NONE) { + phpCAS::trace("Session terminated"); + } else { + phpCAS::error("Session was not terminated"); + phpCAS::trace("Session was not terminated"); + } + $lang = $this->getLangObj(); + $this->printHTMLHeader($lang->getLogout()); + printf('

'.$lang->getShouldHaveBeenRedirected(). '

', $cas_url); + $this->printHTMLFooter(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + + /** + * Check of the current request is a logout request + * + * @return bool is logout request. + */ + private function _isLogoutRequest() + { + return !empty($_POST['logoutRequest']); + } + + /** + * This method handles logout requests. + * + * @param bool $check_client true to check the client bofore handling + * the request, false not to perform any access control. True by default. + * @param bool $allowed_clients an array of host names allowed to send + * logout requests. + * + * @return void + */ + public function handleLogoutRequests($check_client=true, $allowed_clients=false) + { + phpCAS::traceBegin(); + if (!$this->_isLogoutRequest()) { + phpCAS::trace("Not a logout request"); + phpCAS::traceEnd(); + return; + } + if (!$this->getChangeSessionID() + && is_null($this->_signoutCallbackFunction) + ) { + phpCAS::trace( + "phpCAS can't handle logout requests if it is not allowed to change session_id." + ); + } + phpCAS::trace("Logout requested"); + $decoded_logout_rq = urldecode($_POST['logoutRequest']); + phpCAS::trace("SAML REQUEST: ".$decoded_logout_rq); + $allowed = false; + if ($check_client) { + if (!$allowed_clients) { + $allowed_clients = array( $this->_getServerHostname() ); + } + $client_ip = $_SERVER['REMOTE_ADDR']; + $client = gethostbyaddr($client_ip); + phpCAS::trace("Client: ".$client."/".$client_ip); + foreach ($allowed_clients as $allowed_client) { + if (($client == $allowed_client) + || ($client_ip == $allowed_client) + ) { + phpCAS::trace( + "Allowed client '".$allowed_client + ."' matches, logout request is allowed" + ); + $allowed = true; + break; + } else { + phpCAS::trace( + "Allowed client '".$allowed_client."' does not match" + ); + } + } + } else { + phpCAS::trace("No access control set"); + $allowed = true; + } + // If Logout command is permitted proceed with the logout + if ($allowed) { + phpCAS::trace("Logout command allowed"); + // Rebroadcast the logout request + if ($this->_rebroadcast && !isset($_POST['rebroadcast'])) { + $this->_rebroadcast(self::LOGOUT); + } + // Extract the ticket from the SAML Request + preg_match( + "|(.*)|", + $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3 + ); + $wrappedSamlSessionIndex = preg_replace( + '||', '', $tick[0][0] + ); + $ticket2logout = preg_replace( + '||', '', $wrappedSamlSessionIndex + ); + phpCAS::trace("Ticket to logout: ".$ticket2logout); + + // call the post-authenticate callback if registered. + if ($this->_signoutCallbackFunction) { + $args = $this->_signoutCallbackArgs; + array_unshift($args, $ticket2logout); + call_user_func_array($this->_signoutCallbackFunction, $args); + } + + // If phpCAS is managing the session_id, destroy session thanks to + // session_id. + if ($this->getChangeSessionID()) { + $session_id = $this->_sessionIdForTicket($ticket2logout); + phpCAS::trace("Session id: ".$session_id); + + // destroy a possible application session created before phpcas + if (session_id() !== "") { + session_unset(); + session_destroy(); + } + // fix session ID + session_id($session_id); + $_COOKIE[session_name()]=$session_id; + $_GET[session_name()]=$session_id; + + // Overwrite session + session_start(); + session_unset(); + session_destroy(); + phpCAS::trace("Session ". $session_id . " destroyed"); + } + } else { + phpCAS::error("Unauthorized logout request from client '".$client."'"); + phpCAS::trace("Unauthorized logout request from client '".$client."'"); + } + flush(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX BASIC CLIENT FEATURES (CAS 1.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // ST + // ######################################################################## + /** + * @addtogroup internalBasic + * @{ + */ + + /** + * The Ticket provided in the URL of the request if present + * (empty otherwise). Written by CAS_Client::CAS_Client(), read by + * CAS_Client::getTicket() and CAS_Client::_hasPGT(). + * + * @hideinitializer + */ + private $_ticket = ''; + + /** + * This method returns the Service Ticket provided in the URL of the request. + * + * @return string service ticket. + */ + public function getTicket() + { + return $this->_ticket; + } + + /** + * This method stores the Service Ticket. + * + * @param string $st The Service Ticket. + * + * @return void + */ + public function setTicket($st) + { + $this->_ticket = $st; + } + + /** + * This method tells if a Service Ticket was stored. + * + * @return bool if a Service Ticket has been stored. + */ + public function hasTicket() + { + return !empty($this->_ticket); + } + + /** @} */ + + // ######################################################################## + // ST VALIDATION + // ######################################################################## + /** + * @addtogroup internalBasic + * @{ + */ + + /** + * the certificate of the CAS server CA. + * + * @hideinitializer + */ + private $_cas_server_ca_cert = null; + + + /** + + * validate CN of the CAS server certificate + + * + + * @hideinitializer + + */ + + private $_cas_server_cn_validate = true; + + /** + * Set to true not to validate the CAS server. + * + * @hideinitializer + */ + private $_no_cas_server_validation = false; + + + /** + * Set the CA certificate of the CAS server. + * + * @param string $cert the PEM certificate file name of the CA that emited + * the cert of the server + * @param bool $validate_cn valiate CN of the CAS server certificate + * + * @return void + */ + public function setCasServerCACert($cert, $validate_cn) + { + // Argument validation + if (gettype($cert) != 'string') { + throw new CAS_TypeMismatchException($cert, '$cert', 'string'); + } + if (gettype($validate_cn) != 'boolean') { + throw new CAS_TypeMismatchException($validate_cn, '$validate_cn', 'boolean'); + } + if ( !file_exists($cert) && $this->_requestImplementation !== 'CAS_TestHarness_DummyRequest'){ + throw new CAS_InvalidArgumentException("Certificate file does not exist " . $this->_requestImplementation); + } + $this->_cas_server_ca_cert = $cert; + $this->_cas_server_cn_validate = $validate_cn; + } + + /** + * Set no SSL validation for the CAS server. + * + * @return void + */ + public function setNoCasServerValidation() + { + $this->_no_cas_server_validation = true; + } + + /** + * This method is used to validate a CAS 1,0 ticket; halt on failure, and + * sets $validate_url, $text_reponse and $tree_response on success. + * + * @param string &$validate_url reference to the the URL of the request to + * the CAS server. + * @param string &$text_response reference to the response of the CAS + * server, as is (XML text). + * @param string &$tree_response reference to the response of the CAS + * server, as a DOM XML tree. + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + */ + public function validateCAS10(&$validate_url,&$text_response,&$tree_response,$renew=false) + { + phpCAS::traceBegin(); + $result = false; + // build the URL to validate the ticket + $validate_url = $this->getServerServiceValidateURL() + .'&ticket='.urlencode($this->getTicket()); + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + + // open and read the URL + if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'CAS 1.0 ticket not validated', $validate_url, + true/*$no_response*/ + ); + $result = false; + } + + if (preg_match('/^no\n/', $text_response)) { + phpCAS::trace('Ticket has not been validated'); + throw new CAS_AuthenticationException( + $this, 'ST not validated', $validate_url, false/*$no_response*/, + false/*$bad_response*/, $text_response + ); + $result = false; + } else if (!preg_match('/^yes\n/', $text_response)) { + phpCAS::trace('ill-formed response'); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + $result = false; + } + // ticket has been validated, extract the user name + $arr = preg_split('/\n/', $text_response); + $this->_setUser(trim($arr[1])); + $result = true; + + if ($result) { + $this->_renameSession($this->getTicket()); + } + // at this step, ticket has been validated and $this->_user has been set, + phpCAS::traceEnd(true); + return true; + } + + /** @} */ + + + // ######################################################################## + // SAML VALIDATION + // ######################################################################## + /** + * @addtogroup internalSAML + * @{ + */ + + /** + * This method is used to validate a SAML TICKET; halt on failure, and sets + * $validate_url, $text_reponse and $tree_response on success. These + * parameters are used later by CAS_Client::_validatePGT() for CAS proxies. + * + * @param string &$validate_url reference to the the URL of the request to + * the CAS server. + * @param string &$text_response reference to the response of the CAS + * server, as is (XML text). + * @param string &$tree_response reference to the response of the CAS + * server, as a DOM XML tree. + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + */ + public function validateSA(&$validate_url,&$text_response,&$tree_response,$renew=false) + { + phpCAS::traceBegin(); + $result = false; + // build the URL to validate the ticket + $validate_url = $this->getServerSamlValidateURL(); + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + + // open and read the URL + if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, true/*$no_response*/ + ); + } + + phpCAS::trace('server version: '.$this->getServerVersion()); + + // analyze the result depending on the version + switch ($this->getServerVersion()) { + case SAML_VERSION_1_1: + // create new DOMDocument Object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + // read the response of the CAS server into a DOM object + if (!($dom->loadXML($text_response))) { + phpCAS::trace('dom->loadXML() failed'); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + $result = false; + } + // read the root node of the XML tree + if (!($tree_response = $dom->documentElement)) { + phpCAS::trace('documentElement() failed'); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + $result = false; + } else if ( $tree_response->localName != 'Envelope' ) { + // insure that tag name is 'Envelope' + phpCAS::trace( + 'bad XML root node (should be `Envelope\' instead of `' + .$tree_response->localName.'\'' + ); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + $result = false; + } else if ($tree_response->getElementsByTagName("NameIdentifier")->length != 0) { + // check for the NameIdentifier tag in the SAML response + $success_elements = $tree_response->getElementsByTagName("NameIdentifier"); + phpCAS::trace('NameIdentifier found'); + $user = trim($success_elements->item(0)->nodeValue); + phpCAS::trace('user = `'.$user.'`'); + $this->_setUser($user); + $this->_setSessionAttributes($text_response); + $result = true; + } else { + phpCAS::trace('no tag found in SAML payload'); + throw new CAS_AuthenticationException( + $this, 'SA not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + $result = false; + } + } + if ($result) { + $this->_renameSession($this->getTicket()); + } + // at this step, ST has been validated and $this->_user has been set, + phpCAS::traceEnd($result); + return $result; + } + + /** + * This method will parse the DOM and pull out the attributes from the SAML + * payload and put them into an array, then put the array into the session. + * + * @param string $text_response the SAML payload. + * + * @return bool true when successfull and false if no attributes a found + */ + private function _setSessionAttributes($text_response) + { + phpCAS::traceBegin(); + + $result = false; + + $attr_array = array(); + + // create new DOMDocument Object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + if (($dom->loadXML($text_response))) { + $xPath = new DOMXpath($dom); + $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol'); + $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion'); + $nodelist = $xPath->query("//saml:Attribute"); + + if ($nodelist) { + foreach ($nodelist as $node) { + $xres = $xPath->query("saml:AttributeValue", $node); + $name = $node->getAttribute("AttributeName"); + $value_array = array(); + foreach ($xres as $node2) { + $value_array[] = $node2->nodeValue; + } + $attr_array[$name] = $value_array; + } + // UGent addition... + foreach ($attr_array as $attr_key => $attr_value) { + if (count($attr_value) > 1) { + $this->_attributes[$attr_key] = $attr_value; + phpCAS::trace("* " . $attr_key . "=" . print_r($attr_value, true)); + } else { + $this->_attributes[$attr_key] = $attr_value[0]; + phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]); + } + } + $result = true; + } else { + phpCAS::trace("SAML Attributes are empty"); + $result = false; + } + } + phpCAS::traceEnd($result); + return $result; + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX PROXY FEATURES (CAS 2.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // PROXYING + // ######################################################################## + /** + * @addtogroup internalProxy + * @{ + */ + + /** + * A boolean telling if the client is a CAS proxy or not. Written by + * CAS_Client::CAS_Client(), read by CAS_Client::isProxy(). + */ + private $_proxy; + + /** + * Handler for managing service cookies. + */ + private $_serviceCookieJar; + + /** + * Tells if a CAS client is a CAS proxy or not + * + * @return true when the CAS client is a CAs proxy, false otherwise + */ + public function isProxy() + { + return $this->_proxy; + } + + + /** @} */ + // ######################################################################## + // PGT + // ######################################################################## + /** + * @addtogroup internalProxy + * @{ + */ + + /** + * the Proxy Grnting Ticket given by the CAS server (empty otherwise). + * Written by CAS_Client::_setPGT(), read by CAS_Client::_getPGT() and + * CAS_Client::_hasPGT(). + * + * @hideinitializer + */ + private $_pgt = ''; + + /** + * This method returns the Proxy Granting Ticket given by the CAS server. + * + * @return string the Proxy Granting Ticket. + */ + private function _getPGT() + { + return $this->_pgt; + } + + /** + * This method stores the Proxy Granting Ticket. + * + * @param string $pgt The Proxy Granting Ticket. + * + * @return void + */ + private function _setPGT($pgt) + { + $this->_pgt = $pgt; + } + + /** + * This method tells if a Proxy Granting Ticket was stored. + * + * @return true if a Proxy Granting Ticket has been stored. + */ + private function _hasPGT() + { + return !empty($this->_pgt); + } + + /** @} */ + + // ######################################################################## + // CALLBACK MODE + // ######################################################################## + /** + * @addtogroup internalCallback + * @{ + */ + /** + * each PHP script using phpCAS in proxy mode is its own callback to get the + * PGT back from the CAS server. callback_mode is detected by the constructor + * thanks to the GET parameters. + */ + + /** + * a boolean to know if the CAS client is running in callback mode. Written by + * CAS_Client::setCallBackMode(), read by CAS_Client::_isCallbackMode(). + * + * @hideinitializer + */ + private $_callback_mode = false; + + /** + * This method sets/unsets callback mode. + * + * @param bool $callback_mode true to set callback mode, false otherwise. + * + * @return void + */ + private function _setCallbackMode($callback_mode) + { + $this->_callback_mode = $callback_mode; + } + + /** + * This method returns true when the CAs client is running i callback mode, + * false otherwise. + * + * @return A boolean. + */ + private function _isCallbackMode() + { + return $this->_callback_mode; + } + + /** + * the URL that should be used for the PGT callback (in fact the URL of the + * current request without any CGI parameter). Written and read by + * CAS_Client::_getCallbackURL(). + * + * @hideinitializer + */ + private $_callback_url = ''; + + /** + * This method returns the URL that should be used for the PGT callback (in + * fact the URL of the current request without any CGI parameter, except if + * phpCAS::setFixedCallbackURL() was used). + * + * @return The callback URL + */ + private function _getCallbackURL() + { + // the URL is built when needed only + if ( empty($this->_callback_url) ) { + $final_uri = ''; + // remove the ticket if present in the URL + $final_uri = 'https://'; + $final_uri .= $this->_getClientUrl(); + $request_uri = $_SERVER['REQUEST_URI']; + $request_uri = preg_replace('/\?.*$/', '', $request_uri); + $final_uri .= $request_uri; + $this->_callback_url = $final_uri; + } + return $this->_callback_url; + } + + /** + * This method sets the callback url. + * + * @param string $url url to set callback + * + * @return void + */ + public function setCallbackURL($url) + { + // Sequence validation + $this->ensureIsProxy(); + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_callback_url = $url; + } + + /** + * This method is called by CAS_Client::CAS_Client() when running in callback + * mode. It stores the PGT and its PGT Iou, prints its output and halts. + * + * @return void + */ + private function _callback() + { + phpCAS::traceBegin(); + if (preg_match('/^PGTIOU-[\.\-\w]+$/', $_GET['pgtIou'])) { + if (preg_match('/^[PT]GT-[\.\-\w]+$/', $_GET['pgtId'])) { + $this->printHTMLHeader('phpCAS callback'); + $pgt_iou = $_GET['pgtIou']; + $pgt = $_GET['pgtId']; + phpCAS::trace('Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\')'); + echo '

Storing PGT `'.$pgt.'\' (id=`'.$pgt_iou.'\').

'; + $this->_storePGT($pgt, $pgt_iou); + $this->printHTMLFooter(); + phpCAS::traceExit("Successfull Callback"); + } else { + phpCAS::error('PGT format invalid' . $_GET['pgtId']); + phpCAS::traceExit('PGT format invalid' . $_GET['pgtId']); + } + } else { + phpCAS::error('PGTiou format invalid' . $_GET['pgtIou']); + phpCAS::traceExit('PGTiou format invalid' . $_GET['pgtIou']); + } + + // Flush the buffer to prevent from sending anything other then a 200 + // Success Status back to the CAS Server. The Exception would normally + // report as a 500 error. + flush(); + throw new CAS_GracefullTerminationException(); + } + + + /** @} */ + + // ######################################################################## + // PGT STORAGE + // ######################################################################## + /** + * @addtogroup internalPGTStorage + * @{ + */ + + /** + * an instance of a class inheriting of PGTStorage, used to deal with PGT + * storage. Created by CAS_Client::setPGTStorageFile(), used + * by CAS_Client::setPGTStorageFile() and CAS_Client::_initPGTStorage(). + * + * @hideinitializer + */ + private $_pgt_storage = null; + + /** + * This method is used to initialize the storage of PGT's. + * Halts on error. + * + * @return void + */ + private function _initPGTStorage() + { + // if no SetPGTStorageXxx() has been used, default to file + if ( !is_object($this->_pgt_storage) ) { + $this->setPGTStorageFile(); + } + + // initializes the storage + $this->_pgt_storage->init(); + } + + /** + * This method stores a PGT. Halts on error. + * + * @param string $pgt the PGT to store + * @param string $pgt_iou its corresponding Iou + * + * @return void + */ + private function _storePGT($pgt,$pgt_iou) + { + // ensure that storage is initialized + $this->_initPGTStorage(); + // writes the PGT + $this->_pgt_storage->write($pgt, $pgt_iou); + } + + /** + * This method reads a PGT from its Iou and deletes the corresponding + * storage entry. + * + * @param string $pgt_iou the PGT Iou + * + * @return mul The PGT corresponding to the Iou, false when not found. + */ + private function _loadPGT($pgt_iou) + { + // ensure that storage is initialized + $this->_initPGTStorage(); + // read the PGT + return $this->_pgt_storage->read($pgt_iou); + } + + /** + * This method can be used to set a custom PGT storage object. + * + * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that + * inherits from the CAS_PGTStorage_AbstractStorage class + * + * @return void + */ + public function setPGTStorage($storage) + { + // Sequence validation + $this->ensureIsProxy(); + + // check that the storage has not already been set + if ( is_object($this->_pgt_storage) ) { + phpCAS::error('PGT storage already defined'); + } + + // check to make sure a valid storage object was specified + if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) ) + throw new CAS_TypeMismatchException($storage, '$storage', 'CAS_PGTStorage_AbstractStorage object'); + + // store the PGTStorage object + $this->_pgt_storage = $storage; + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests in a database. + * + * @param string $dsn_or_pdo a dsn string to use for creating a PDO + * object or a PDO object + * @param string $username the username to use when connecting to the + * database + * @param string $password the password to use when connecting to the + * database + * @param string $table the table to use for storing and retrieving + * PGTs + * @param string $driver_options any driver options to use when connecting + * to the database + * + * @return void + */ + public function setPGTStorageDb( + $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null + ) { + // Sequence validation + $this->ensureIsProxy(); + + // Argument validation + if (!(is_object($dsn_or_pdo) && $dsn_or_pdo instanceof PDO) && gettype($dsn_or_pdo) != 'string') + throw new CAS_TypeMismatchException($dsn_or_pdo, '$dsn_or_pdo', 'string or PDO object'); + if (gettype($username) != 'string') + throw new CAS_TypeMismatchException($username, '$username', 'string'); + if (gettype($password) != 'string') + throw new CAS_TypeMismatchException($password, '$password', 'string'); + if (gettype($table) != 'string') + throw new CAS_TypeMismatchException($table, '$password', 'string'); + + // create the storage object + $this->setPGTStorage( + new CAS_PGTStorage_Db( + $this, $dsn_or_pdo, $username, $password, $table, $driver_options + ) + ); + } + + /** + * This method is used to tell phpCAS to store the response of the + * CAS server to PGT requests onto the filesystem. + * + * @param string $path the path where the PGT's should be stored + * + * @return void + */ + public function setPGTStorageFile($path='') + { + // Sequence validation + $this->ensureIsProxy(); + + // Argument validation + if (gettype($path) != 'string') + throw new CAS_TypeMismatchException($path, '$path', 'string'); + + // create the storage object + $this->setPGTStorage(new CAS_PGTStorage_File($this, $path)); + } + + + // ######################################################################## + // PGT VALIDATION + // ######################################################################## + /** + * This method is used to validate a PGT; halt on failure. + * + * @param string &$validate_url the URL of the request to the CAS server. + * @param string $text_response the response of the CAS server, as is + * (XML text); result of + * CAS_Client::validateCAS10() or + * CAS_Client::validateCAS20(). + * @param string $tree_response the response of the CAS server, as a DOM XML + * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20(). + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + */ + private function _validatePGT(&$validate_url,$text_response,$tree_response) + { + phpCAS::traceBegin(); + if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) { + phpCAS::trace(' not found'); + // authentication succeded, but no PGT Iou was transmitted + throw new CAS_AuthenticationException( + $this, 'Ticket validated but no PGT Iou transmitted', + $validate_url, false/*$no_response*/, false/*$bad_response*/, + $text_response + ); + } else { + // PGT Iou transmitted, extract it + $pgt_iou = trim( + $tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue + ); + if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgt_iou)) { + $pgt = $this->_loadPGT($pgt_iou); + if ( $pgt == false ) { + phpCAS::trace('could not load PGT'); + throw new CAS_AuthenticationException( + $this, + 'PGT Iou was transmitted but PGT could not be retrieved', + $validate_url, false/*$no_response*/, + false/*$bad_response*/, $text_response + ); + } + $this->_setPGT($pgt); + } else { + phpCAS::trace('PGTiou format error'); + throw new CAS_AuthenticationException( + $this, 'PGT Iou was transmitted but has wrong format', + $validate_url, false/*$no_response*/, false/*$bad_response*/, + $text_response + ); + } + } + phpCAS::traceEnd(true); + return true; + } + + // ######################################################################## + // PGT VALIDATION + // ######################################################################## + + /** + * This method is used to retrieve PT's from the CAS server thanks to a PGT. + * + * @param string $target_service the service to ask for with the PT. + * @param string &$err_code an error code (PHPCAS_SERVICE_OK on success). + * @param string &$err_msg an error message (empty on success). + * + * @return a Proxy Ticket, or false on error. + */ + public function retrievePT($target_service,&$err_code,&$err_msg) + { + // Argument validation + if (gettype($target_service) != 'string') + throw new CAS_TypeMismatchException($target_service, '$target_service', 'string'); + + phpCAS::traceBegin(); + + // by default, $err_msg is set empty and $pt to true. On error, $pt is + // set to false and $err_msg to an error message. At the end, if $pt is false + // and $error_msg is still empty, it is set to 'invalid response' (the most + // commonly encountered error). + $err_msg = ''; + + // build the URL to retrieve the PT + $cas_url = $this->getServerProxyURL().'?targetService=' + .urlencode($target_service).'&pgt='.$this->_getPGT(); + + // open and read the URL + if ( !$this->_readURL($cas_url, $headers, $cas_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')' + ); + $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE; + $err_msg = 'could not retrieve PT (no response from the CAS server)'; + phpCAS::traceEnd(false); + return false; + } + + $bad_response = false; + + if ( !$bad_response ) { + // create new DOMDocument object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + // read the response of the CAS server into a DOM object + if ( !($dom->loadXML($cas_response))) { + phpCAS::trace('dom->loadXML() failed'); + // read failed + $bad_response = true; + } + } + + if ( !$bad_response ) { + // read the root node of the XML tree + if ( !($root = $dom->documentElement) ) { + phpCAS::trace('documentElement failed'); + // read failed + $bad_response = true; + } + } + + if ( !$bad_response ) { + // insure that tag name is 'serviceResponse' + if ( $root->localName != 'serviceResponse' ) { + phpCAS::trace('localName failed'); + // bad root node + $bad_response = true; + } + } + + if ( !$bad_response ) { + // look for a proxySuccess tag + if ( $root->getElementsByTagName("proxySuccess")->length != 0) { + $proxy_success_list = $root->getElementsByTagName("proxySuccess"); + + // authentication succeded, look for a proxyTicket tag + if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) { + $err_code = PHPCAS_SERVICE_OK; + $err_msg = ''; + $pt = trim( + $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue + ); + phpCAS::trace('original PT: '.trim($pt)); + phpCAS::traceEnd($pt); + return $pt; + } else { + phpCAS::trace(' was found, but not '); + } + } else if ($root->getElementsByTagName("proxyFailure")->length != 0) { + // look for a proxyFailure tag + $proxy_failure_list = $root->getElementsByTagName("proxyFailure"); + + // authentication failed, extract the error + $err_code = PHPCAS_SERVICE_PT_FAILURE; + $err_msg = 'PT retrieving failed (code=`' + .$proxy_failure_list->item(0)->getAttribute('code') + .'\', message=`' + .trim($proxy_failure_list->item(0)->nodeValue) + .'\')'; + phpCAS::traceEnd(false); + return false; + } else { + phpCAS::trace('neither nor found'); + } + } + + // at this step, we are sure that the response of the CAS server was + // illformed + $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE; + $err_msg = 'Invalid response from the CAS server (response=`' + .$cas_response.'\')'; + + phpCAS::traceEnd(false); + return false; + } + + /** @} */ + + // ######################################################################## + // READ CAS SERVER ANSWERS + // ######################################################################## + + /** + * @addtogroup internalMisc + * @{ + */ + + /** + * This method is used to acces a remote URL. + * + * @param string $url the URL to access. + * @param string &$headers an array containing the HTTP header lines of the + * response (an empty array on failure). + * @param string &$body the body of the response, as a string (empty on + * failure). + * @param string &$err_msg an error message, filled on failure. + * + * @return true on success, false otherwise (in this later case, $err_msg + * contains an error message). + */ + private function _readURL($url, &$headers, &$body, &$err_msg) + { + phpCAS::traceBegin(); + $className = $this->_requestImplementation; + $request = new $className(); + + if (count($this->_curl_options)) { + $request->setCurlOptions($this->_curl_options); + } + + $request->setUrl($url); + + if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) { + phpCAS::error( + 'one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.' + ); + } + if ($this->_cas_server_ca_cert != '') { + $request->setSslCaCert( + $this->_cas_server_ca_cert, $this->_cas_server_cn_validate + ); + } + + // add extra stuff if SAML + if ($this->getServerVersion() == SAML_VERSION_1_1) { + $request->addHeader("soapaction: http://www.oasis-open.org/committees/security"); + $request->addHeader("cache-control: no-cache"); + $request->addHeader("pragma: no-cache"); + $request->addHeader("accept: text/xml"); + $request->addHeader("connection: keep-alive"); + $request->addHeader("content-type: text/xml"); + $request->makePost(); + $request->setPostBody($this->_buildSAMLPayload()); + } + + if ($request->send()) { + $headers = $request->getResponseHeaders(); + $body = $request->getResponseBody(); + $err_msg = ''; + phpCAS::traceEnd(true); + return true; + } else { + $headers = ''; + $body = ''; + $err_msg = $request->getErrorMessage(); + phpCAS::traceEnd(false); + return false; + } + } + + /** + * This method is used to build the SAML POST body sent to /samlValidate URL. + * + * @return the SOAP-encased SAMLP artifact (the ticket). + */ + private function _buildSAMLPayload() + { + phpCAS::traceBegin(); + + //get the ticket + $sa = urlencode($this->getTicket()); + + $body = SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST + .SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE + .SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE; + + phpCAS::traceEnd($body); + return ($body); + } + + /** @} **/ + + // ######################################################################## + // ACCESS TO EXTERNAL SERVICES + // ######################################################################## + + /** + * @addtogroup internalProxyServices + * @{ + */ + + + /** + * Answer a proxy-authenticated service handler. + * + * @param string $type The service type. One of: + * PHPCAS_PROXIED_SERVICE_HTTP_GET, PHPCAS_PROXIED_SERVICE_HTTP_POST, + * PHPCAS_PROXIED_SERVICE_IMAP + * + * @return CAS_ProxiedService + * @throws InvalidArgumentException If the service type is unknown. + */ + public function getProxiedService ($type) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($type) != 'string') + throw new CAS_TypeMismatchException($type, '$type', 'string'); + + switch ($type) { + case PHPCAS_PROXIED_SERVICE_HTTP_GET: + case PHPCAS_PROXIED_SERVICE_HTTP_POST: + $requestClass = $this->_requestImplementation; + $request = new $requestClass(); + if (count($this->_curl_options)) { + $request->setCurlOptions($this->_curl_options); + } + $proxiedService = new $type($request, $this->_serviceCookieJar); + if ($proxiedService instanceof CAS_ProxiedService_Testable) { + $proxiedService->setCasClient($this); + } + return $proxiedService; + case PHPCAS_PROXIED_SERVICE_IMAP; + $proxiedService = new CAS_ProxiedService_Imap($this->_getUser()); + if ($proxiedService instanceof CAS_ProxiedService_Testable) { + $proxiedService->setCasClient($this); + } + return $proxiedService; + default: + throw new CAS_InvalidArgumentException( + "Unknown proxied-service type, $type." + ); + } + } + + /** + * Initialize a proxied-service handler with the proxy-ticket it should use. + * + * @param CAS_ProxiedService $proxiedService service handler + * + * @return void + * + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure getting the + * url from the proxied service. + */ + public function initializeProxiedService (CAS_ProxiedService $proxiedService) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + $url = $proxiedService->getServiceUrl(); + if (!is_string($url)) { + throw new CAS_ProxiedService_Exception( + "Proxied Service ".get_class($proxiedService) + ."->getServiceUrl() should have returned a string, returned a " + .gettype($url)." instead." + ); + } + $pt = $this->retrievePT($url, $err_code, $err_msg); + if (!$pt) { + throw new CAS_ProxyTicketException($err_msg, $err_code); + } + $proxiedService->setProxyTicket($pt); + } + + /** + * This method is used to access an HTTP[S] service. + * + * @param string $url the service to access. + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$output the output of the service (also used to give an error + * message on failure). + * + * @return true on success, false otherwise (in this later case, $err_code + * gives the reason why it failed and $output contains an error message). + */ + public function serviceWeb($url,&$err_code,&$output) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + try { + $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET); + $service->setUrl($url); + $service->send(); + $output = $service->getResponseBody(); + $err_code = PHPCAS_SERVICE_OK; + return true; + } catch (CAS_ProxyTicketException $e) { + $err_code = $e->getCode(); + $output = $e->getMessage(); + return false; + } catch (CAS_ProxiedService_Exception $e) { + $lang = $this->getLangObj(); + $output = sprintf( + $lang->getServiceUnavailable(), $url, $e->getMessage() + ); + $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; + return false; + } + } + + /** + * This method is used to access an IMAP/POP3/NNTP service. + * + * @param string $url a string giving the URL of the service, including + * the mailing box for IMAP URLs, as accepted by imap_open(). + * @param string $serviceUrl a string giving for CAS retrieve Proxy ticket + * @param string $flags options given to imap_open(). + * @param int &$err_code an error code Possible values are + * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE, + * PHPCAS_SERVICE_NOT_AVAILABLE. + * @param string &$err_msg an error message on failure + * @param string &$pt the Proxy Ticket (PT) retrieved from the CAS + * server to access the URL on success, false on error). + * + * @return object an IMAP stream on success, false otherwise (in this later + * case, $err_code gives the reason why it failed and $err_msg contains an + * error message). + */ + public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt) + { + // Sequence validation + $this->ensureIsProxy(); + $this->ensureAuthenticationCallSuccessful(); + + // Argument validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + if (gettype($serviceUrl) != 'string') + throw new CAS_TypeMismatchException($serviceUrl, '$serviceUrl', 'string'); + if (gettype($flags) != 'integer') + throw new CAS_TypeMismatchException($flags, '$flags', 'string'); + + try { + $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP); + $service->setServiceUrl($serviceUrl); + $service->setMailbox($url); + $service->setOptions($flags); + + $stream = $service->open(); + $err_code = PHPCAS_SERVICE_OK; + $pt = $service->getImapProxyTicket(); + return $stream; + } catch (CAS_ProxyTicketException $e) { + $err_msg = $e->getMessage(); + $err_code = $e->getCode(); + $pt = false; + return false; + } catch (CAS_ProxiedService_Exception $e) { + $lang = $this->getLangObj(); + $err_msg = sprintf( + $lang->getServiceUnavailable(), + $url, + $e->getMessage() + ); + $err_code = PHPCAS_SERVICE_NOT_AVAILABLE; + $pt = false; + return false; + } + } + + /** @} **/ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX PROXIED CLIENT FEATURES (CAS 2.0) XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + // ######################################################################## + // PT + // ######################################################################## + /** + * @addtogroup internalService + * @{ + */ + + /** + * This array will store a list of proxies in front of this application. This + * property will only be populated if this script is being proxied rather than + * accessed directly. + * + * It is set in CAS_Client::validateCAS20() and can be read by + * CAS_Client::getProxies() + * + * @access private + */ + private $_proxies = array(); + + /** + * Answer an array of proxies that are sitting in front of this application. + * + * This method will only return a non-empty array if we have received and + * validated a Proxy Ticket. + * + * @return array + * @access public + */ + public function getProxies() + { + return $this->_proxies; + } + + /** + * Set the Proxy array, probably from persistant storage. + * + * @param array $proxies An array of proxies + * + * @return void + * @access private + */ + private function _setProxies($proxies) + { + $this->_proxies = $proxies; + if (!empty($proxies)) { + // For proxy-authenticated requests people are not viewing the URL + // directly since the client is another application making a + // web-service call. + // Because of this, stripping the ticket from the URL is unnecessary + // and causes another web-service request to be performed. Additionally, + // if session handling on either the client or the server malfunctions + // then the subsequent request will not complete successfully. + $this->setNoClearTicketsFromUrl(); + } + } + + /** + * A container of patterns to be allowed as proxies in front of the cas client. + * + * @var CAS_ProxyChain_AllowedList + */ + private $_allowed_proxy_chains; + + /** + * Answer the CAS_ProxyChain_AllowedList object for this client. + * + * @return CAS_ProxyChain_AllowedList + */ + public function getAllowedProxyChains () + { + if (empty($this->_allowed_proxy_chains)) { + $this->_allowed_proxy_chains = new CAS_ProxyChain_AllowedList(); + } + return $this->_allowed_proxy_chains; + } + + /** @} */ + // ######################################################################## + // PT VALIDATION + // ######################################################################## + /** + * @addtogroup internalProxied + * @{ + */ + + /** + * This method is used to validate a cas 2.0 ST or PT; halt on failure + * Used for all CAS 2.0 validations + * + * @param string &$validate_url the url of the reponse + * @param string &$text_response the text of the repsones + * @param string &$tree_response the domxml tree of the respones + * @param bool $renew true to force the authentication with the CAS server + * + * @return bool true when successfull and issue a CAS_AuthenticationException + * and false on an error + */ + public function validateCAS20(&$validate_url,&$text_response,&$tree_response, $renew=false) + { + phpCAS::traceBegin(); + phpCAS::trace($text_response); + $result = false; + // build the URL to validate the ticket + if ($this->getAllowedProxyChains()->isProxyingAllowed()) { + $validate_url = $this->getServerProxyValidateURL().'&ticket=' + .urlencode($this->getTicket()); + } else { + $validate_url = $this->getServerServiceValidateURL().'&ticket=' + .urlencode($this->getTicket()); + } + + if ( $this->isProxy() ) { + // pass the callback url for CAS proxies + $validate_url .= '&pgtUrl='.urlencode($this->_getCallbackURL()); + } + + if ( $renew ) { + // pass the renew + $validate_url .= '&renew=true'; + } + + // open and read the URL + if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) { + phpCAS::trace( + 'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')' + ); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + true/*$no_response*/ + ); + $result = false; + } + + // create new DOMDocument object + $dom = new DOMDocument(); + // Fix possible whitspace problems + $dom->preserveWhiteSpace = false; + // CAS servers should only return data in utf-8 + $dom->encoding = "utf-8"; + // read the response of the CAS server into a DOMDocument object + if ( !($dom->loadXML($text_response))) { + // read failed + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + $result = false; + } else if ( !($tree_response = $dom->documentElement) ) { + // read the root node of the XML tree + // read failed + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + $result = false; + } else if ($tree_response->localName != 'serviceResponse') { + // insure that tag name is 'serviceResponse' + // bad root node + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + $result = false; + } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) { + // authentication failed, extract the error code and message and throw exception + $auth_fail_list = $tree_response + ->getElementsByTagName("authenticationFailure"); + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, false/*$bad_response*/, + $text_response, + $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/, + trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/ + ); + $result = false; + } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) { + // authentication succeded, extract the user name + $success_elements = $tree_response + ->getElementsByTagName("authenticationSuccess"); + if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) { + // no user specified => error + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, $text_response + ); + $result = false; + } else { + $this->_setUser( + trim( + $success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue + ) + ); + $this->_readExtraAttributesCas20($success_elements); + // Store the proxies we are sitting behind for authorization checking + $proxyList = array(); + if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) { + foreach ($arr as $proxyElem) { + phpCAS::trace("Found Proxy: ".$proxyElem->nodeValue); + $proxyList[] = trim($proxyElem->nodeValue); + } + $this->_setProxies($proxyList); + phpCAS::trace("Storing Proxy List"); + } + // Check if the proxies in front of us are allowed + if (!$this->getAllowedProxyChains()->isProxyListAllowed($proxyList)) { + throw new CAS_AuthenticationException( + $this, 'Proxy not allowed', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + $result = false; + } else { + $result = true; + } + } + } else { + throw new CAS_AuthenticationException( + $this, 'Ticket not validated', $validate_url, + false/*$no_response*/, true/*$bad_response*/, + $text_response + ); + $result = false; + } + if ($result) { + $this->_renameSession($this->getTicket()); + } + // at this step, Ticket has been validated and $this->_user has been set, + + phpCAS::traceEnd($result); + return $result; + } + + + /** + * This method will parse the DOM and pull out the attributes from the XML + * payload and put them into an array, then put the array into the session. + * + * @param string $success_elements payload of the response + * + * @return bool true when successfull, halt otherwise by calling + * CAS_Client::_authError(). + */ + private function _readExtraAttributesCas20($success_elements) + { + phpCAS::traceBegin(); + + $extra_attributes = array(); + + // "Jasig Style" Attributes: + // + // + // + // jsmith + // + // RubyCAS + // Smith + // John + // CN=Staff,OU=Groups,DC=example,DC=edu + // CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu + // + // PGTIOU-84678-8a9d2sfa23casd + // + // + // + if ($this->_casAttributeParserCallbackFunction !== null + && is_callable($this->_casAttributeParserCallbackFunction) + ) { + array_unshift($this->_casAttributeParserCallbackArgs, $success_elements->item(0)); + phpCas :: trace("Calling attritubeParser callback"); + $extra_attributes = call_user_func_array( + $this->_casAttributeParserCallbackFunction, + $this->_casAttributeParserCallbackArgs + ); + } elseif ( $success_elements->item(0)->getElementsByTagName("attributes")->length != 0) { + $attr_nodes = $success_elements->item(0) + ->getElementsByTagName("attributes"); + phpCas :: trace("Found nested jasig style attributes"); + if ($attr_nodes->item(0)->hasChildNodes()) { + // Nested Attributes + foreach ($attr_nodes->item(0)->childNodes as $attr_child) { + phpCas :: trace( + "Attribute [".$attr_child->localName."] = " + .$attr_child->nodeValue + ); + $this->_addAttributeToArray( + $extra_attributes, $attr_child->localName, + $attr_child->nodeValue + ); + } + } + } else { + // "RubyCAS Style" attributes + // + // + // + // jsmith + // + // RubyCAS + // Smith + // John + // CN=Staff,OU=Groups,DC=example,DC=edu + // CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu + // + // PGTIOU-84678-8a9d2sfa23casd + // + // + // + phpCas :: trace("Testing for rubycas style attributes"); + $childnodes = $success_elements->item(0)->childNodes; + foreach ($childnodes as $attr_node) { + switch ($attr_node->localName) { + case 'user': + case 'proxies': + case 'proxyGrantingTicket': + break; + default: + if (strlen(trim($attr_node->nodeValue))) { + phpCas :: trace( + "Attribute [".$attr_node->localName."] = ".$attr_node->nodeValue + ); + $this->_addAttributeToArray( + $extra_attributes, $attr_node->localName, + $attr_node->nodeValue + ); + } + } + } + } + + // "Name-Value" attributes. + // + // Attribute format from these mailing list thread: + // http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html + // Note: This is a less widely used format, but in use by at least two institutions. + // + // + // + // jsmith + // + // + // + // + // + // + // + // PGTIOU-84678-8a9d2sfa23casd + // + // + // + if (!count($extra_attributes) + && $success_elements->item(0)->getElementsByTagName("attribute")->length != 0 + ) { + $attr_nodes = $success_elements->item(0) + ->getElementsByTagName("attribute"); + $firstAttr = $attr_nodes->item(0); + if (!$firstAttr->hasChildNodes() + && $firstAttr->hasAttribute('name') + && $firstAttr->hasAttribute('value') + ) { + phpCas :: trace("Found Name-Value style attributes"); + // Nested Attributes + foreach ($attr_nodes as $attr_node) { + if ($attr_node->hasAttribute('name') + && $attr_node->hasAttribute('value') + ) { + phpCas :: trace( + "Attribute [".$attr_node->getAttribute('name') + ."] = ".$attr_node->getAttribute('value') + ); + $this->_addAttributeToArray( + $extra_attributes, $attr_node->getAttribute('name'), + $attr_node->getAttribute('value') + ); + } + } + } + } + + $this->setAttributes($extra_attributes); + phpCAS::traceEnd(); + return true; + } + + /** + * Add an attribute value to an array of attributes. + * + * @param array &$attributeArray reference to array + * @param string $name name of attribute + * @param string $value value of attribute + * + * @return void + */ + private function _addAttributeToArray(array &$attributeArray, $name, $value) + { + // If multiple attributes exist, add as an array value + if (isset($attributeArray[$name])) { + // Initialize the array with the existing value + if (!is_array($attributeArray[$name])) { + $existingValue = $attributeArray[$name]; + $attributeArray[$name] = array($existingValue); + } + + $attributeArray[$name][] = trim($value); + } else { + $attributeArray[$name] = trim($value); + } + } + + /** @} */ + + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + // XX XX + // XX MISC XX + // XX XX + // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + + /** + * @addtogroup internalMisc + * @{ + */ + + // ######################################################################## + // URL + // ######################################################################## + /** + * the URL of the current request (without any ticket CGI parameter). Written + * and read by CAS_Client::getURL(). + * + * @hideinitializer + */ + private $_url = ''; + + + /** + * This method sets the URL of the current request + * + * @param string $url url to set for service + * + * @return void + */ + public function setURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + $this->_url = $url; + } + + /** + * This method returns the URL of the current request (without any ticket + * CGI parameter). + * + * @return The URL + */ + public function getURL() + { + phpCAS::traceBegin(); + // the URL is built when needed only + if ( empty($this->_url) ) { + $final_uri = ''; + // remove the ticket if present in the URL + $final_uri = ($this->_isHttps()) ? 'https' : 'http'; + $final_uri .= '://'; + + $final_uri .= $this->_getClientUrl(); + $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2); + $final_uri .= $request_uri[0]; + + if (isset($request_uri[1]) && $request_uri[1]) { + $query_string= $this->_removeParameterFromQueryString('ticket', $request_uri[1]); + + // If the query string still has anything left, + // append it to the final URI + if ($query_string !== '') { + $final_uri .= "?$query_string"; + } + } + + phpCAS::trace("Final URI: $final_uri"); + $this->setURL($final_uri); + } + phpCAS::traceEnd($this->_url); + return $this->_url; + } + + /** + * This method sets the base URL of the CAS server. + * + * @param string $url the base URL + * + * @return string base url + */ + public function setBaseURL($url) + { + // Argument Validation + if (gettype($url) != 'string') + throw new CAS_TypeMismatchException($url, '$url', 'string'); + + return $this->_server['base_url'] = $url; + } + + + /** + * Try to figure out the phpCas client URL with possible Proxys / Ports etc. + * + * @return string Server URL with domain:port + */ + private function _getClientUrl() + { + $server_url = ''; + if (!empty($_SERVER['HTTP_X_FORWARDED_HOST'])) { + // explode the host list separated by comma and use the first host + $hosts = explode(',', $_SERVER['HTTP_X_FORWARDED_HOST']); + // see rfc7239#5.3 and rfc7230#2.7.1: port is in HTTP_X_FORWARDED_HOST if non default + return $hosts[0]; + } else if (!empty($_SERVER['HTTP_X_FORWARDED_SERVER'])) { + $server_url = $_SERVER['HTTP_X_FORWARDED_SERVER']; + } else { + if (empty($_SERVER['SERVER_NAME'])) { + $server_url = $_SERVER['HTTP_HOST']; + } else { + $server_url = $_SERVER['SERVER_NAME']; + } + } + if (!strpos($server_url, ':')) { + if (empty($_SERVER['HTTP_X_FORWARDED_PORT'])) { + $server_port = $_SERVER['SERVER_PORT']; + } else { + $ports = explode(',', $_SERVER['HTTP_X_FORWARDED_PORT']); + $server_port = $ports[0]; + } + + if ( ($this->_isHttps() && $server_port!=443) + || (!$this->_isHttps() && $server_port!=80) + ) { + $server_url .= ':'; + $server_url .= $server_port; + } + } + return $server_url; + } + + /** + * This method checks to see if the request is secured via HTTPS + * + * @return bool true if https, false otherwise + */ + private function _isHttps() + { + if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + return ($_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'); + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTOCOL'])) { + return ($_SERVER['HTTP_X_FORWARDED_PROTOCOL'] === 'https'); + } elseif ( isset($_SERVER['HTTPS']) + && !empty($_SERVER['HTTPS']) + && strcasecmp($_SERVER['HTTPS'], 'off') !== 0 + ) { + return true; + } + return false; + + } + + /** + * Removes a parameter from a query string + * + * @param string $parameterName name of parameter + * @param string $queryString query string + * + * @return string new query string + * + * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string + */ + private function _removeParameterFromQueryString($parameterName, $queryString) + { + $parameterName = preg_quote($parameterName); + return preg_replace( + "/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/", + '', $queryString + ); + } + + /** + * This method is used to append query parameters to an url. Since the url + * might already contain parameter it has to be detected and to build a proper + * URL + * + * @param string $url base url to add the query params to + * @param string $query params in query form with & separated + * + * @return url with query params + */ + private function _buildQueryUrl($url, $query) + { + $url .= (strstr($url, '?') === false) ? '?' : '&'; + $url .= $query; + return $url; + } + + /** + * Renaming the session + * + * @param string $ticket name of the ticket + * + * @return void + */ + private function _renameSession($ticket) + { + phpCAS::traceBegin(); + if ($this->getChangeSessionID()) { + if (!empty($this->_user)) { + $old_session = $_SESSION; + phpCAS :: trace("Killing session: ". session_id()); + session_destroy(); + // set up a new session, of name based on the ticket + $session_id = $this->_sessionIdForTicket($ticket); + phpCAS :: trace("Starting session: ". $session_id); + session_id($session_id); + session_start(); + phpCAS :: trace("Restoring old session vars"); + $_SESSION = $old_session; + } else { + phpCAS :: trace ( + 'Session should only be renamed after successfull authentication' + ); + } + } else { + phpCAS :: trace( + "Skipping session rename since phpCAS is not handling the session." + ); + } + phpCAS::traceEnd(); + } + + /** + * Answer a valid session-id given a CAS ticket. + * + * The output must be deterministic to allow single-log-out when presented with + * the ticket to log-out. + * + * + * @param string $ticket name of the ticket + * + * @return string + */ + private function _sessionIdForTicket($ticket) + { + // Hash the ticket to ensure that the value meets the PHP 7.1 requirement + // that session-ids have a length between 22 and 256 characters. + return hash('sha256', $this->_sessionIdSalt . $ticket); + } + + /** + * Set a salt/seed for the session-id hash to make it harder to guess. + * + * @var string $_sessionIdSalt + */ + private $_sessionIdSalt = ''; + + /** + * Set a salt/seed for the session-id hash to make it harder to guess. + * + * @param string $salt + * + * @return void + */ + public function setSessionIdSalt($salt) { + $this->_sessionIdSalt = (string)$salt; + } + + // ######################################################################## + // AUTHENTICATION ERROR HANDLING + // ######################################################################## + /** + * This method is used to print the HTML output when the user was not + * authenticated. + * + * @param string $failure the failure that occured + * @param string $cas_url the URL the CAS server was asked for + * @param bool $no_response the response from the CAS server (other + * parameters are ignored if true) + * @param bool $bad_response bad response from the CAS server ($err_code + * and $err_msg ignored if true) + * @param string $cas_response the response of the CAS server + * @param int $err_code the error code given by the CAS server + * @param string $err_msg the error message given by the CAS server + * + * @return void + */ + private function _authError( + $failure, + $cas_url, + $no_response, + $bad_response='', + $cas_response='', + $err_code='', + $err_msg='' + ) { + phpCAS::traceBegin(); + $lang = $this->getLangObj(); + $this->printHTMLHeader($lang->getAuthenticationFailed()); + printf( + $lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()), + isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:'' + ); + phpCAS::trace('CAS URL: '.$cas_url); + phpCAS::trace('Authentication failure: '.$failure); + if ( $no_response ) { + phpCAS::trace('Reason: no response from the CAS server'); + } else { + if ( $bad_response ) { + phpCAS::trace('Reason: bad response from the CAS server'); + } else { + switch ($this->getServerVersion()) { + case CAS_VERSION_1_0: + phpCAS::trace('Reason: CAS error'); + break; + case CAS_VERSION_2_0: + case CAS_VERSION_3_0: + if ( empty($err_code) ) { + phpCAS::trace('Reason: no CAS error'); + } else { + phpCAS::trace( + 'Reason: ['.$err_code.'] CAS error: '.$err_msg + ); + } + break; + } + } + phpCAS::trace('CAS response: '.$cas_response); + } + $this->printHTMLFooter(); + phpCAS::traceExit(); + throw new CAS_GracefullTerminationException(); + } + + // ######################################################################## + // PGTIOU/PGTID and logoutRequest rebroadcasting + // ######################################################################## + + /** + * Boolean of whether to rebroadcast pgtIou/pgtId and logoutRequest, and + * array of the nodes. + */ + private $_rebroadcast = false; + private $_rebroadcast_nodes = array(); + + /** + * Constants used for determining rebroadcast node type. + */ + const HOSTNAME = 0; + const IP = 1; + + /** + * Determine the node type from the URL. + * + * @param String $nodeURL The node URL. + * + * @return string hostname + * + */ + private function _getNodeType($nodeURL) + { + phpCAS::traceBegin(); + if (preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $nodeURL)) { + phpCAS::traceEnd(self::IP); + return self::IP; + } else { + phpCAS::traceEnd(self::HOSTNAME); + return self::HOSTNAME; + } + } + + /** + * Store the rebroadcast node for pgtIou/pgtId and logout requests. + * + * @param string $rebroadcastNodeUrl The rebroadcast node URL. + * + * @return void + */ + public function addRebroadcastNode($rebroadcastNodeUrl) + { + // Argument validation + if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl)) + throw new CAS_TypeMismatchException($rebroadcastNodeUrl, '$rebroadcastNodeUrl', 'url'); + + // Store the rebroadcast node and set flag + $this->_rebroadcast = true; + $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl; + } + + /** + * An array to store extra rebroadcast curl options. + */ + private $_rebroadcast_headers = array(); + + /** + * This method is used to add header parameters when rebroadcasting + * pgtIou/pgtId or logoutRequest. + * + * @param string $header Header to send when rebroadcasting. + * + * @return void + */ + public function addRebroadcastHeader($header) + { + if (gettype($header) != 'string') + throw new CAS_TypeMismatchException($header, '$header', 'string'); + + $this->_rebroadcast_headers[] = $header; + } + + /** + * Constants used for determining rebroadcast type (logout or pgtIou/pgtId). + */ + const LOGOUT = 0; + const PGTIOU = 1; + + /** + * This method rebroadcasts logout/pgtIou requests. Can be LOGOUT,PGTIOU + * + * @param int $type type of rebroadcasting. + * + * @return void + */ + private function _rebroadcast($type) + { + phpCAS::traceBegin(); + + $rebroadcast_curl_options = array( + CURLOPT_FAILONERROR => 1, + CURLOPT_FOLLOWLOCATION => 1, + CURLOPT_RETURNTRANSFER => 1, + CURLOPT_CONNECTTIMEOUT => 1, + CURLOPT_TIMEOUT => 4); + + // Try to determine the IP address of the server + if (!empty($_SERVER['SERVER_ADDR'])) { + $ip = $_SERVER['SERVER_ADDR']; + } else if (!empty($_SERVER['LOCAL_ADDR'])) { + // IIS 7 + $ip = $_SERVER['LOCAL_ADDR']; + } + // Try to determine the DNS name of the server + if (!empty($ip)) { + $dns = gethostbyaddr($ip); + } + $multiClassName = 'CAS_Request_CurlMultiRequest'; + $multiRequest = new $multiClassName(); + + for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) { + if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false)) + || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false)) + ) { + phpCAS::trace( + 'Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i] + .$_SERVER['REQUEST_URI'] + ); + $className = $this->_requestImplementation; + $request = new $className(); + + $url = $this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI']; + $request->setUrl($url); + + if (count($this->_rebroadcast_headers)) { + $request->addHeaders($this->_rebroadcast_headers); + } + + $request->makePost(); + if ($type == self::LOGOUT) { + // Logout request + $request->setPostBody( + 'rebroadcast=false&logoutRequest='.$_POST['logoutRequest'] + ); + } else if ($type == self::PGTIOU) { + // pgtIou/pgtId rebroadcast + $request->setPostBody('rebroadcast=false'); + } + + $request->setCurlOptions($rebroadcast_curl_options); + + $multiRequest->addRequest($request); + } else { + phpCAS::trace( + 'Rebroadcast not sent to self: ' + .$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'') + .'/'.(!empty($dns)?$dns:'') + ); + } + } + // We need at least 1 request + if ($multiRequest->getNumRequests() > 0) { + $multiRequest->send(); + } + phpCAS::traceEnd(); + } + + /** @} */ +} + +?> diff --git a/phpCas/source/CAS/CookieJar.php b/phpCas/source/CAS/CookieJar.php new file mode 100644 index 0000000..549b892 --- /dev/null +++ b/phpCas/source/CAS/CookieJar.php @@ -0,0 +1,385 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class provides access to service cookies and handles parsing of response + * headers to pull out cookie values. + * + * @class CAS_CookieJar + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_CookieJar +{ + + private $_cookies; + + /** + * Create a new cookie jar by passing it a reference to an array in which it + * should store cookies. + * + * @param array &$storageArray Array to store cookies + * + * @return void + */ + public function __construct (array &$storageArray) + { + $this->_cookies =& $storageArray; + } + + /** + * Store cookies for a web service request. + * Cookie storage is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt + * + * @param string $request_url The URL that generated the response headers. + * @param array $response_headers An array of the HTTP response header strings. + * + * @return void + * + * @access private + */ + public function storeCookies ($request_url, $response_headers) + { + $urlParts = parse_url($request_url); + $defaultDomain = $urlParts['host']; + + $cookies = $this->parseCookieHeaders($response_headers, $defaultDomain); + + // var_dump($cookies); + foreach ($cookies as $cookie) { + // Enforce the same-origin policy by verifying that the cookie + // would match the url that is setting it + if (!$this->cookieMatchesTarget($cookie, $urlParts)) { + continue; + } + + // store the cookie + $this->storeCookie($cookie); + + phpCAS::trace($cookie['name'].' -> '.$cookie['value']); + } + } + + /** + * Retrieve cookies applicable for a web service request. + * Cookie applicability is based on RFC 2965: http://www.ietf.org/rfc/rfc2965.txt + * + * @param string $request_url The url that the cookies will be for. + * + * @return array An array containing cookies. E.g. array('name' => 'val'); + * + * @access private + */ + public function getCookies ($request_url) + { + if (!count($this->_cookies)) { + return array(); + } + + // If our request URL can't be parsed, no cookies apply. + $target = parse_url($request_url); + if ($target === false) { + return array(); + } + + $this->expireCookies(); + + $matching_cookies = array(); + foreach ($this->_cookies as $key => $cookie) { + if ($this->cookieMatchesTarget($cookie, $target)) { + $matching_cookies[$cookie['name']] = $cookie['value']; + } + } + return $matching_cookies; + } + + + /** + * Parse Cookies without PECL + * From the comments in http://php.net/manual/en/function.http-parse-cookie.php + * + * @param array $header array of header lines. + * @param string $defaultDomain The domain to use if none is specified in + * the cookie. + * + * @return array of cookies + */ + protected function parseCookieHeaders( $header, $defaultDomain ) + { + phpCAS::traceBegin(); + $cookies = array(); + foreach ( $header as $line ) { + if ( preg_match('/^Set-Cookie2?: /i', $line)) { + $cookies[] = $this->parseCookieHeader($line, $defaultDomain); + } + } + + phpCAS::traceEnd($cookies); + return $cookies; + } + + /** + * Parse a single cookie header line. + * + * Based on RFC2965 http://www.ietf.org/rfc/rfc2965.txt + * + * @param string $line The header line. + * @param string $defaultDomain The domain to use if none is specified in + * the cookie. + * + * @return array + */ + protected function parseCookieHeader ($line, $defaultDomain) + { + if (!$defaultDomain) { + throw new CAS_InvalidArgumentException( + '$defaultDomain was not provided.' + ); + } + + // Set our default values + $cookie = array( + 'domain' => $defaultDomain, + 'path' => '/', + 'secure' => false, + ); + + $line = preg_replace('/^Set-Cookie2?: /i', '', trim($line)); + + // trim any trailing semicolons. + $line = trim($line, ';'); + + phpCAS::trace("Cookie Line: $line"); + + // This implementation makes the assumption that semicolons will not + // be present in quoted attribute values. While attribute values that + // contain semicolons are allowed by RFC2965, they are hopefully rare + // enough to ignore for our purposes. Most browsers make the same + // assumption. + $attributeStrings = explode(';', $line); + + foreach ( $attributeStrings as $attributeString ) { + // split on the first equals sign and use the rest as value + $attributeParts = explode('=', $attributeString, 2); + + $attributeName = trim($attributeParts[0]); + $attributeNameLC = strtolower($attributeName); + + if (isset($attributeParts[1])) { + $attributeValue = trim($attributeParts[1]); + // Values may be quoted strings. + if (strpos($attributeValue, '"') === 0) { + $attributeValue = trim($attributeValue, '"'); + // unescape any escaped quotes: + $attributeValue = str_replace('\"', '"', $attributeValue); + } + } else { + $attributeValue = null; + } + + switch ($attributeNameLC) { + case 'expires': + $cookie['expires'] = strtotime($attributeValue); + break; + case 'max-age': + $cookie['max-age'] = (int)$attributeValue; + // Set an expiry time based on the max-age + if ($cookie['max-age']) { + $cookie['expires'] = time() + $cookie['max-age']; + } else { + // If max-age is zero, then the cookie should be removed + // imediately so set an expiry before now. + $cookie['expires'] = time() - 1; + } + break; + case 'secure': + $cookie['secure'] = true; + break; + case 'domain': + case 'path': + case 'port': + case 'version': + case 'comment': + case 'commenturl': + case 'discard': + case 'httponly': + $cookie[$attributeNameLC] = $attributeValue; + break; + default: + $cookie['name'] = $attributeName; + $cookie['value'] = $attributeValue; + } + } + + return $cookie; + } + + /** + * Add, update, or remove a cookie. + * + * @param array $cookie A cookie array as created by parseCookieHeaders() + * + * @return void + * + * @access protected + */ + protected function storeCookie ($cookie) + { + // Discard any old versions of this cookie. + $this->discardCookie($cookie); + $this->_cookies[] = $cookie; + + } + + /** + * Discard an existing cookie + * + * @param array $cookie An cookie + * + * @return void + * + * @access protected + */ + protected function discardCookie ($cookie) + { + if (!isset($cookie['domain']) + || !isset($cookie['path']) + || !isset($cookie['path']) + ) { + throw new CAS_InvalidArgumentException('Invalid Cookie array passed.'); + } + + foreach ($this->_cookies as $key => $old_cookie) { + if ( $cookie['domain'] == $old_cookie['domain'] + && $cookie['path'] == $old_cookie['path'] + && $cookie['name'] == $old_cookie['name'] + ) { + unset($this->_cookies[$key]); + } + } + } + + /** + * Go through our stored cookies and remove any that are expired. + * + * @return void + * + * @access protected + */ + protected function expireCookies () + { + foreach ($this->_cookies as $key => $cookie) { + if (isset($cookie['expires']) && $cookie['expires'] < time()) { + unset($this->_cookies[$key]); + } + } + } + + /** + * Answer true if cookie is applicable to a target. + * + * @param array $cookie An array of cookie attributes. + * @param array $target An array of URL attributes as generated by parse_url(). + * + * @return bool + * + * @access private + */ + protected function cookieMatchesTarget ($cookie, $target) + { + if (!is_array($target)) { + throw new CAS_InvalidArgumentException( + '$target must be an array of URL attributes as generated by parse_url().' + ); + } + if (!isset($target['host'])) { + throw new CAS_InvalidArgumentException( + '$target must be an array of URL attributes as generated by parse_url().' + ); + } + + // Verify that the scheme matches + if ($cookie['secure'] && $target['scheme'] != 'https') { + return false; + } + + // Verify that the host matches + // Match domain and mulit-host cookies + if (strpos($cookie['domain'], '.') === 0) { + // .host.domain.edu cookies are valid for host.domain.edu + if (substr($cookie['domain'], 1) == $target['host']) { + // continue with other checks + } else { + // non-exact host-name matches. + // check that the target host a.b.c.edu is within .b.c.edu + $pos = strripos($target['host'], $cookie['domain']); + if (!$pos) { + return false; + } + // verify that the cookie domain is the last part of the host. + if ($pos + strlen($cookie['domain']) != strlen($target['host'])) { + return false; + } + // verify that the host name does not contain interior dots as per + // RFC 2965 section 3.3.2 Rejecting Cookies + // http://www.ietf.org/rfc/rfc2965.txt + $hostname = substr($target['host'], 0, $pos); + if (strpos($hostname, '.') !== false) { + return false; + } + } + } else { + // If the cookie host doesn't begin with '.', + // the host must case-insensitive match exactly + if (strcasecmp($target['host'], $cookie['domain']) !== 0) { + return false; + } + } + + // Verify that the port matches + if (isset($cookie['ports']) + && !in_array($target['port'], $cookie['ports']) + ) { + return false; + } + + // Verify that the path matches + if (strpos($target['path'], $cookie['path']) !== 0) { + return false; + } + + return true; + } + +} + +?> diff --git a/phpCas/source/CAS/Exception.php b/phpCas/source/CAS/Exception.php new file mode 100644 index 0000000..d956d19 --- /dev/null +++ b/phpCas/source/CAS/Exception.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * A root exception interface for all exceptions in phpCAS. + * + * All exceptions thrown in phpCAS should implement this interface to allow them + * to be caught as a category by clients. Each phpCAS exception should extend + * an appropriate SPL exception class that best fits its type. + * + * For example, an InvalidArgumentException in phpCAS should be defined as + * + * class CAS_InvalidArgumentException + * extends InvalidArgumentException + * implements CAS_Exception + * { } + * + * This definition allows the CAS_InvalidArgumentException to be caught as either + * an InvalidArgumentException or as a CAS_Exception. + * + * @class CAS_Exception + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + */ +interface CAS_Exception +{ + +} +?> \ No newline at end of file diff --git a/phpCas/source/CAS/GracefullTerminationException.php b/phpCas/source/CAS/GracefullTerminationException.php new file mode 100644 index 0000000..6d845df --- /dev/null +++ b/phpCas/source/CAS/GracefullTerminationException.php @@ -0,0 +1,86 @@ + + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An exception for terminatinating execution or to throw for unit testing + * + * @class CAS_GracefullTerminationException.php + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_GracefullTerminationException +extends RuntimeException +implements CAS_Exception +{ + + /** + * Test if exceptions should be thrown or if we should just exit. + * In production usage we want to just exit cleanly when prompting the user + * for a redirect without filling the error logs with uncaught exceptions. + * In unit testing scenarios we cannot exit or we won't be able to continue + * with our tests. + * + * @param string $message Message Text + * @param string $code Error code + * + * @return void + */ + public function __construct ($message = 'Terminate Gracefully', $code = 0) + { + // Exit cleanly to avoid filling up the logs with uncaught exceptions. + if (self::$_exitWhenThrown) { + exit; + } else { + // Throw exceptions to allow unit testing to continue; + parent::__construct($message, $code); + } + } + + private static $_exitWhenThrown = true; + /** + * Force phpcas to thow Exceptions instead of calling exit() + * Needed for unit testing. Generally shouldn't be used in production due to + * an increase in Apache error logging if CAS_GracefulTerminiationExceptions + * are not caught and handled. + * + * @return void + */ + public static function throwInsteadOfExiting() + { + self::$_exitWhenThrown = false; + } + +} +?> \ No newline at end of file diff --git a/phpCas/source/CAS/InvalidArgumentException.php b/phpCas/source/CAS/InvalidArgumentException.php new file mode 100644 index 0000000..ba43d39 --- /dev/null +++ b/phpCas/source/CAS/InvalidArgumentException.php @@ -0,0 +1,46 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Exception that denotes invalid arguments were passed. + * + * @class CAS_InvalidArgumentException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_InvalidArgumentException +extends InvalidArgumentException +implements CAS_Exception +{ + +} +?> \ No newline at end of file diff --git a/phpCas/source/CAS/Languages/Catalan.php b/phpCas/source/CAS/Languages/Catalan.php new file mode 100644 index 0000000..a0b64d8 --- /dev/null +++ b/phpCas/source/CAS/Languages/Catalan.php @@ -0,0 +1,114 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Catalan language class + * + * @class CAS_Languages_Catalan + * @category Authentication + * @package PhpCAS + * @author Iván-Benjamín García Torà + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_Catalan implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'usant servidor'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'Autentificació CAS necessària!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'Sortida de CAS necessària!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Ja hauria d\ haver estat redireccionat al servidor CAS. Feu click aquí per a continuar.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'Autentificació CAS fallida!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

No estàs autentificat.

Pots tornar a intentar-ho fent click aquí.

Si el problema persisteix hauría de contactar amb l\'administrador d\'aquest llocc.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'El servei `%s\' no està disponible (%s).'; + } +} diff --git a/phpCas/source/CAS/Languages/ChineseSimplified.php b/phpCas/source/CAS/Languages/ChineseSimplified.php new file mode 100644 index 0000000..bb66593 --- /dev/null +++ b/phpCas/source/CAS/Languages/ChineseSimplified.php @@ -0,0 +1,114 @@ +, Phy25 + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Chinese Simplified language class + * + * @class CAS_Languages_ChineseSimplified + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry , Phy25 + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_ChineseSimplified implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return '连接的服务器'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return '请进行 CAS 认证!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return '请进行 CAS 登出!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return '你正被重定向到 CAS 服务器。点击这里继续。'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'CAS 认证失败!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

你没有成功登录。

你可以点击这里重新登录

如果问题依然存在,请联系本站管理员

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return '服务器 %s 不可用(%s)。'; + } +} \ No newline at end of file diff --git a/phpCas/source/CAS/Languages/English.php b/phpCas/source/CAS/Languages/English.php new file mode 100644 index 0000000..002c1ba --- /dev/null +++ b/phpCas/source/CAS/Languages/English.php @@ -0,0 +1,114 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * English language class + * + * @class CAS_Languages_English + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_English implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'using server'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'CAS Authentication wanted!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'CAS logout wanted!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'You should already have been redirected to the CAS server. Click here to continue.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'CAS Authentication failed!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

You were not authenticated.

You may submit your request again by clicking here.

If the problem persists, you may contact the administrator of this site.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'The service `%s\' is not available (%s).'; + } +} \ No newline at end of file diff --git a/phpCas/source/CAS/Languages/French.php b/phpCas/source/CAS/Languages/French.php new file mode 100644 index 0000000..b99847a --- /dev/null +++ b/phpCas/source/CAS/Languages/French.php @@ -0,0 +1,116 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * French language class + * + * @class CAS_Languages_French + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_French implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'utilisant le serveur'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'Authentication CAS nécessaire !'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'Déconnexion demandée !'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Vous auriez du etre redirigé(e) vers le serveur CAS. Cliquez ici pour continuer.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'Authentification CAS infructueuse !'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

Vous n\'avez pas été authentifié(e).

Vous pouvez soumettre votre requete à nouveau en cliquant ici.

Si le problème persiste, vous pouvez contacter l\'administrateur de ce site.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'Le service `%s\' est indisponible (%s)'; + } +} + +?> \ No newline at end of file diff --git a/phpCas/source/CAS/Languages/German.php b/phpCas/source/CAS/Languages/German.php new file mode 100644 index 0000000..ed3150a --- /dev/null +++ b/phpCas/source/CAS/Languages/German.php @@ -0,0 +1,116 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * German language class + * + * @class CAS_Languages_German + * @category Authentication + * @package PhpCAS + * @author Henrik Genssen + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_German implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'via Server'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'CAS Authentifizierung erforderlich!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'CAS Abmeldung!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'eigentlich häten Sie zum CAS Server weitergeleitet werden sollen. Drücken Sie hier um fortzufahren.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'CAS Anmeldung fehlgeschlagen!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

Sie wurden nicht angemeldet.

Um es erneut zu versuchen klicken Sie hier.

Wenn das Problem bestehen bleibt, kontaktieren Sie den Administrator dieser Seite.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'Der Dienst `%s\' ist nicht verfügbar (%s).'; + } +} + +?> diff --git a/phpCas/source/CAS/Languages/Greek.php b/phpCas/source/CAS/Languages/Greek.php new file mode 100644 index 0000000..888ce24 --- /dev/null +++ b/phpCas/source/CAS/Languages/Greek.php @@ -0,0 +1,115 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Greek language class + * + * @class CAS_Languages_Greek + * @category Authentication + * @package PhpCAS + * @author Vangelis Haniotakis + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_Greek implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'χρησιμοποιείται ο εξυπηρετητής'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'Απαιτείται η ταυτοποίηση CAS!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'Απαιτείται η αποσύνδεση από CAS!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Θα έπρεπε να είχατε ανακατευθυνθεί στον εξυπηρετητή CAS. Κάντε κλίκ εδώ για να συνεχίσετε.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'Η ταυτοποίηση CAS απέτυχε!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

Δεν ταυτοποιηθήκατε.

Μπορείτε να ξαναπροσπαθήσετε, κάνοντας κλίκ εδώ.

Εαν το πρόβλημα επιμείνει, ελάτε σε επαφή με τον διαχειριστή.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'Η υπηρεσία `%s\' δεν είναι διαθέσιμη (%s).'; + } +} +?> diff --git a/phpCas/source/CAS/Languages/Japanese.php b/phpCas/source/CAS/Languages/Japanese.php new file mode 100644 index 0000000..a15bf17 --- /dev/null +++ b/phpCas/source/CAS/Languages/Japanese.php @@ -0,0 +1,113 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Japanese language class. Now Encoding is UTF-8. + * + * @class CAS_Languages_Japanese + * @category Authentication + * @package PhpCAS + * @author fnorif + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + **/ +class CAS_Languages_Japanese implements CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'サーバーを使っています。'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return 'CASによる認証を行います。'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return 'CASからログアウトします!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'CASサーバに行く必要があります。自動的に転送されない場合は こちら をクリックして続行します。'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return 'CASによる認証に失敗しました。'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

認証できませんでした。

もう一度リクエストを送信する場合はこちらをクリック。

問題が解決しない場合は このサイトの管理者に問い合わせてください。

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'サービス `%s\' は利用できません (%s)。'; + } +} +?> diff --git a/phpCas/source/CAS/Languages/LanguageInterface.php b/phpCas/source/CAS/Languages/LanguageInterface.php new file mode 100644 index 0000000..5de93aa --- /dev/null +++ b/phpCas/source/CAS/Languages/LanguageInterface.php @@ -0,0 +1,96 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Language Interface class for all internationalization files + * + * @class CAS_Languages_LanguageInterface + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ + +interface CAS_Languages_LanguageInterface +{ + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer(); + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted(); + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout(); + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected(); + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed(); + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated(); + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable(); + +} +?> \ No newline at end of file diff --git a/phpCas/source/CAS/Languages/Spanish.php b/phpCas/source/CAS/Languages/Spanish.php new file mode 100644 index 0000000..5675a41 --- /dev/null +++ b/phpCas/source/CAS/Languages/Spanish.php @@ -0,0 +1,117 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Spanish language class + * + * @class CAS_Languages_Spanish + * @category Authentication + * @package PhpCAS + * @author Iván-Benjamín García Torà + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + + * @sa @link internalLang Internationalization @endlink + * @ingroup internalLang + */ +class CAS_Languages_Spanish implements CAS_Languages_LanguageInterface +{ + + /** + * Get the using server string + * + * @return string using server + */ + public function getUsingServer() + { + return 'usando servidor'; + } + + /** + * Get authentication wanted string + * + * @return string authentication wanted + */ + public function getAuthenticationWanted() + { + return '¡Autentificación CAS necesaria!'; + } + + /** + * Get logout string + * + * @return string logout + */ + public function getLogout() + { + return '¡Salida CAS necesaria!'; + } + + /** + * Get the should have been redirected string + * + * @return string should habe been redirected + */ + public function getShouldHaveBeenRedirected() + { + return 'Ya debería haber sido redireccionado al servidor CAS. Haga click aquí para continuar.'; + } + + /** + * Get authentication failed string + * + * @return string authentication failed + */ + public function getAuthenticationFailed() + { + return '¡Autentificación CAS fallida!'; + } + + /** + * Get the your were not authenticated string + * + * @return string not authenticated + */ + public function getYouWereNotAuthenticated() + { + return '

No estás autentificado.

Puedes volver a intentarlo haciendo click aquí.

Si el problema persiste debería contactar con el administrador de este sitio.

'; + } + + /** + * Get the service unavailable string + * + * @return string service unavailable + */ + public function getServiceUnavailable() + { + return 'El servicio `%s\' no está disponible (%s).'; + } +} +?> diff --git a/phpCas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php b/phpCas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php new file mode 100644 index 0000000..ef83097 --- /dev/null +++ b/phpCas/source/CAS/OutOfSequenceBeforeAuthenticationCallException.php @@ -0,0 +1,56 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. In this case it should be thrown when an + * authentication call has not yet happened. + * + * @class CAS_OutOfSequenceBeforeAuthenticationCallException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceBeforeAuthenticationCallException +extends CAS_OutOfSequenceException +implements CAS_Exception +{ + /** + * Return standard error meessage + * + * @return void + */ + public function __construct () + { + parent::__construct('An authentication call hasn\'t happened yet.'); + } +} diff --git a/phpCas/source/CAS/OutOfSequenceBeforeClientException.php b/phpCas/source/CAS/OutOfSequenceBeforeClientException.php new file mode 100644 index 0000000..f1ea7e2 --- /dev/null +++ b/phpCas/source/CAS/OutOfSequenceBeforeClientException.php @@ -0,0 +1,58 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. In this case it should be thrown when the client() or + * proxy() call has not yet happened and no client or proxy object exists. + * + * @class CAS_OutOfSequenceBeforeClientException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceBeforeClientException +extends CAS_OutOfSequenceException +implements CAS_Exception +{ + /** + * Return standard error message + * + * @return void + */ + public function __construct () + { + parent::__construct( + 'this method cannot be called before phpCAS::client() or phpCAS::proxy()' + ); + } +} diff --git a/phpCas/source/CAS/OutOfSequenceBeforeProxyException.php b/phpCas/source/CAS/OutOfSequenceBeforeProxyException.php new file mode 100644 index 0000000..8038542 --- /dev/null +++ b/phpCas/source/CAS/OutOfSequenceBeforeProxyException.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. In this case it should be thrown when the proxy() call + * has not yet happened and no proxy object exists. + * + * @class CAS_OutOfSequenceBeforeProxyException + * @category Authentication + * @package PhpCAS + * @author Joachim Fritschi + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceBeforeProxyException +extends CAS_OutOfSequenceException +implements CAS_Exception +{ + + /** + * Return standard error message + * + * @return void + */ + public function __construct () + { + parent::__construct( + 'this method cannot be called before phpCAS::proxy()' + ); + } +} diff --git a/phpCas/source/CAS/OutOfSequenceException.php b/phpCas/source/CAS/OutOfSequenceException.php new file mode 100644 index 0000000..d101811 --- /dev/null +++ b/phpCas/source/CAS/OutOfSequenceException.php @@ -0,0 +1,49 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class defines Exceptions that should be thrown when the sequence of + * operations is invalid. Examples are: + * - Requesting the response before executing a request. + * - Changing the URL of a request after executing the request. + * + * @class CAS_OutOfSequenceException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_OutOfSequenceException +extends BadMethodCallException +implements CAS_Exception +{ + +} diff --git a/phpCas/source/CAS/PGTStorage/AbstractStorage.php b/phpCas/source/CAS/PGTStorage/AbstractStorage.php new file mode 100644 index 0000000..c164898 --- /dev/null +++ b/phpCas/source/CAS/PGTStorage/AbstractStorage.php @@ -0,0 +1,222 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Basic class for PGT storage + * The CAS_PGTStorage_AbstractStorage class is a generic class for PGT storage. + * This class should not be instanciated itself but inherited by specific PGT + * storage classes. + * + * @class CAS_PGTStorage_AbstractStorage + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @ingroup internalPGTStorage + */ + +abstract class CAS_PGTStorage_AbstractStorage +{ + /** + * @addtogroup internalPGTStorage + * @{ + */ + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The constructor of the class, should be called only by inherited classes. + * + * @param CAS_Client $cas_parent the CAS _client instance that creates the + * current object. + * + * @return void + * + * @protected + */ + function __construct($cas_parent) + { + phpCAS::traceBegin(); + if ( !$cas_parent->isProxy() ) { + phpCAS::error( + 'defining PGT storage makes no sense when not using a CAS proxy' + ); + } + phpCAS::traceEnd(); + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This virtual method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return void + * + * @public + */ + function getStorageType() + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** + * This virtual method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @return void + * + * @public + */ + function getStorageInfo() + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + // ######################################################################## + // ERROR HANDLING + // ######################################################################## + + /** + * string used to store an error message. Written by + * PGTStorage::setErrorMessage(), read by PGTStorage::getErrorMessage(). + * + * @hideinitializer + * @deprecated not used. + */ + var $_error_message=false; + + /** + * This method sets en error message, which can be read later by + * PGTStorage::getErrorMessage(). + * + * @param string $error_message an error message + * + * @return void + * + * @deprecated not used. + */ + function setErrorMessage($error_message) + { + $this->_error_message = $error_message; + } + + /** + * This method returns an error message set by PGTStorage::setErrorMessage(). + * + * @return an error message when set by PGTStorage::setErrorMessage(), FALSE + * otherwise. + * + * @deprecated not used. + */ + function getErrorMessage() + { + return $this->_error_message; + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * a boolean telling if the storage has already been initialized. Written by + * PGTStorage::init(), read by PGTStorage::isInitialized(). + * + * @hideinitializer + */ + var $_initialized = false; + + /** + * This method tells if the storage has already been intialized. + * + * @return a boolean + * + * @protected + */ + function isInitialized() + { + return $this->_initialized; + } + + /** + * This virtual method initializes the object. + * + * @return void + */ + function init() + { + $this->_initialized = true; + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This virtual method stores a PGT and its corresponding PGT Iuo. + * + * @param string $pgt the PGT + * @param string $pgt_iou the PGT iou + * + * @return void + * + * @note Should never be called. + * + */ + function write($pgt,$pgt_iou) + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** + * This virtual method reads a PGT corresponding to a PGT Iou and deletes + * the corresponding storage entry. + * + * @param string $pgt_iou the PGT iou + * + * @return void + * + * @note Should never be called. + */ + function read($pgt_iou) + { + phpCAS::error(__CLASS__.'::'.__FUNCTION__.'() should never be called'); + } + + /** @} */ + +} + +?> diff --git a/phpCas/source/CAS/PGTStorage/Db.php b/phpCas/source/CAS/PGTStorage/Db.php new file mode 100644 index 0000000..c331ca0 --- /dev/null +++ b/phpCas/source/CAS/PGTStorage/Db.php @@ -0,0 +1,440 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +define('CAS_PGT_STORAGE_DB_DEFAULT_TABLE', 'cas_pgts'); + +/** + * Basic class for PGT database storage + * The CAS_PGTStorage_Db class is a class for PGT database storage. + * + * @class CAS_PGTStorage_Db + * @category Authentication + * @package PhpCAS + * @author Daniel Frett + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * @ingroup internalPGTStorageDb + */ + +class CAS_PGTStorage_Db extends CAS_PGTStorage_AbstractStorage +{ + /** + * @addtogroup internalCAS_PGTStorageDb + * @{ + */ + + /** + * the PDO object to use for database interactions + */ + private $_pdo; + + /** + * This method returns the PDO object to use for database interactions. + * + * @return the PDO object + */ + private function _getPdo() + { + return $this->_pdo; + } + + /** + * database connection options to use when creating a new PDO object + */ + private $_dsn; + private $_username; + private $_password; + private $_table_options; + + /** + * the table to use for storing/retrieving pgt's + */ + private $_table; + + /** + * This method returns the table to use when storing/retrieving PGT's + * + * @return the name of the pgt storage table. + */ + private function _getTable() + { + return $this->_table; + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return an informational string. + */ + public function getStorageType() + { + return "db"; + } + + /** + * This method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @return an informational string. + * @public + */ + public function getStorageInfo() + { + return 'table=`'.$this->_getTable().'\''; + } + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The class constructor. + * + * @param CAS_Client $cas_parent the CAS_Client instance that creates + * the object. + * @param string $dsn_or_pdo a dsn string to use for creating a PDO + * object or a PDO object + * @param string $username the username to use when connecting to + * the database + * @param string $password the password to use when connecting to + * the database + * @param string $table the table to use for storing and + * retrieving PGT's + * @param string $driver_options any driver options to use when + * connecting to the database + */ + public function __construct( + $cas_parent, $dsn_or_pdo, $username='', $password='', $table='', + $driver_options=null + ) { + phpCAS::traceBegin(); + // call the ancestor's constructor + parent::__construct($cas_parent); + + // set default values + if ( empty($table) ) { + $table = CAS_PGT_STORAGE_DB_DEFAULT_TABLE; + } + if ( !is_array($driver_options) ) { + $driver_options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION); + } + + // store the specified parameters + if ($dsn_or_pdo instanceof PDO) { + $this->_pdo = $dsn_or_pdo; + } else { + $this->_dsn = $dsn_or_pdo; + $this->_username = $username; + $this->_password = $password; + $this->_driver_options = $driver_options; + } + + // store the table name + $this->_table = $table; + + phpCAS::traceEnd(); + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * This method is used to initialize the storage. Halts on error. + * + * @return void + */ + public function init() + { + phpCAS::traceBegin(); + // if the storage has already been initialized, return immediatly + if ($this->isInitialized()) { + return; + } + + // initialize the base object + parent::init(); + + // create the PDO object if it doesn't exist already + if (!($this->_pdo instanceof PDO)) { + try { + $this->_pdo = new PDO( + $this->_dsn, $this->_username, $this->_password, + $this->_driver_options + ); + } + catch(PDOException $e) { + phpCAS::error('Database connection error: ' . $e->getMessage()); + } + } + + phpCAS::traceEnd(); + } + + // ######################################################################## + // PDO database interaction + // ######################################################################## + + /** + * attribute that stores the previous error mode for the PDO handle while + * processing a transaction + */ + private $_errMode; + + /** + * This method will enable the Exception error mode on the PDO object + * + * @return void + */ + private function _setErrorMode() + { + // get PDO object and enable exception error mode + $pdo = $this->_getPdo(); + $this->_errMode = $pdo->getAttribute(PDO::ATTR_ERRMODE); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } + + /** + * this method will reset the error mode on the PDO object + * + * @return void + */ + private function _resetErrorMode() + { + // get PDO object and reset the error mode to what it was originally + $pdo = $this->_getPdo(); + $pdo->setAttribute(PDO::ATTR_ERRMODE, $this->_errMode); + } + + // ######################################################################## + // database queries + // ######################################################################## + // these queries are potentially unsafe because the person using this library + // can set the table to use, but there is no reliable way to escape SQL + // fieldnames in PDO yet + + /** + * This method returns the query used to create a pgt storage table + * + * @return the create table SQL, no bind params in query + */ + protected function createTableSql() + { + return 'CREATE TABLE ' . $this->_getTable() + . ' (pgt_iou VARCHAR(255) NOT NULL PRIMARY KEY, pgt VARCHAR(255) NOT NULL)'; + } + + /** + * This method returns the query used to store a pgt + * + * @return the store PGT SQL, :pgt and :pgt_iou are the bind params contained + * in the query + */ + protected function storePgtSql() + { + return 'INSERT INTO ' . $this->_getTable() + . ' (pgt_iou, pgt) VALUES (:pgt_iou, :pgt)'; + } + + /** + * This method returns the query used to retrieve a pgt. the first column + * of the first row should contain the pgt + * + * @return the retrieve PGT SQL, :pgt_iou is the only bind param contained + * in the query + */ + protected function retrievePgtSql() + { + return 'SELECT pgt FROM ' . $this->_getTable() . ' WHERE pgt_iou = :pgt_iou'; + } + + /** + * This method returns the query used to delete a pgt. + * + * @return the delete PGT SQL, :pgt_iou is the only bind param contained in + * the query + */ + protected function deletePgtSql() + { + return 'DELETE FROM ' . $this->_getTable() . ' WHERE pgt_iou = :pgt_iou'; + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This method creates the database table used to store pgt's and pgtiou's + * + * @return void + */ + public function createTable() + { + phpCAS::traceBegin(); + + // initialize this PGTStorage object if it hasn't been initialized yet + if ( !$this->isInitialized() ) { + $this->init(); + } + + // initialize the PDO object for this method + $pdo = $this->_getPdo(); + $this->_setErrorMode(); + + try { + $pdo->beginTransaction(); + + $query = $pdo->query($this->createTableSQL()); + $query->closeCursor(); + + $pdo->commit(); + } + catch(PDOException $e) { + // attempt rolling back the transaction before throwing a phpCAS error + try { + $pdo->rollBack(); + } + catch(PDOException $e) { + } + phpCAS::error('error creating PGT storage table: ' . $e->getMessage()); + } + + // reset the PDO object + $this->_resetErrorMode(); + + phpCAS::traceEnd(); + } + + /** + * This method stores a PGT and its corresponding PGT Iou in the database. + * Echoes a warning on error. + * + * @param string $pgt the PGT + * @param string $pgt_iou the PGT iou + * + * @return void + */ + public function write($pgt, $pgt_iou) + { + phpCAS::traceBegin(); + + // initialize the PDO object for this method + $pdo = $this->_getPdo(); + $this->_setErrorMode(); + + try { + $pdo->beginTransaction(); + + $query = $pdo->prepare($this->storePgtSql()); + $query->bindValue(':pgt', $pgt, PDO::PARAM_STR); + $query->bindValue(':pgt_iou', $pgt_iou, PDO::PARAM_STR); + $query->execute(); + $query->closeCursor(); + + $pdo->commit(); + } + catch(PDOException $e) { + // attempt rolling back the transaction before throwing a phpCAS error + try { + $pdo->rollBack(); + } + catch(PDOException $e) { + } + phpCAS::error('error writing PGT to database: ' . $e->getMessage()); + } + + // reset the PDO object + $this->_resetErrorMode(); + + phpCAS::traceEnd(); + } + + /** + * This method reads a PGT corresponding to a PGT Iou and deletes the + * corresponding db entry. + * + * @param string $pgt_iou the PGT iou + * + * @return the corresponding PGT, or FALSE on error + */ + public function read($pgt_iou) + { + phpCAS::traceBegin(); + $pgt = false; + + // initialize the PDO object for this method + $pdo = $this->_getPdo(); + $this->_setErrorMode(); + + try { + $pdo->beginTransaction(); + + // fetch the pgt for the specified pgt_iou + $query = $pdo->prepare($this->retrievePgtSql()); + $query->bindValue(':pgt_iou', $pgt_iou, PDO::PARAM_STR); + $query->execute(); + $pgt = $query->fetchColumn(0); + $query->closeCursor(); + + // delete the specified pgt_iou from the database + $query = $pdo->prepare($this->deletePgtSql()); + $query->bindValue(':pgt_iou', $pgt_iou, PDO::PARAM_STR); + $query->execute(); + $query->closeCursor(); + + $pdo->commit(); + } + catch(PDOException $e) { + // attempt rolling back the transaction before throwing a phpCAS error + try { + $pdo->rollBack(); + } + catch(PDOException $e) { + } + phpCAS::trace('error reading PGT from database: ' . $e->getMessage()); + } + + // reset the PDO object + $this->_resetErrorMode(); + + phpCAS::traceEnd(); + return $pgt; + } + + /** @} */ + +} + +?> diff --git a/phpCas/source/CAS/PGTStorage/File.php b/phpCas/source/CAS/PGTStorage/File.php new file mode 100644 index 0000000..4be80c5 --- /dev/null +++ b/phpCas/source/CAS/PGTStorage/File.php @@ -0,0 +1,261 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * The CAS_PGTStorage_File class is a class for PGT file storage. An instance of + * this class is returned by CAS_Client::SetPGTStorageFile(). + * + * @class CAS_PGTStorage_File + * @category Authentication + * @package PhpCAS + * @author Pascal Aubry + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + * + * @ingroup internalPGTStorageFile + */ + +class CAS_PGTStorage_File extends CAS_PGTStorage_AbstractStorage +{ + /** + * @addtogroup internalPGTStorageFile + * @{ + */ + + /** + * a string telling where PGT's should be stored on the filesystem. Written by + * PGTStorageFile::PGTStorageFile(), read by getPath(). + * + * @private + */ + var $_path; + + /** + * This method returns the name of the directory where PGT's should be stored + * on the filesystem. + * + * @return the name of a directory (with leading and trailing '/') + * + * @private + */ + function getPath() + { + return $this->_path; + } + + // ######################################################################## + // DEBUGGING + // ######################################################################## + + /** + * This method returns an informational string giving the type of storage + * used by the object (used for debugging purposes). + * + * @return an informational string. + * @public + */ + function getStorageType() + { + return "file"; + } + + /** + * This method returns an informational string giving informations on the + * parameters of the storage.(used for debugging purposes). + * + * @return an informational string. + * @public + */ + function getStorageInfo() + { + return 'path=`'.$this->getPath().'\''; + } + + // ######################################################################## + // CONSTRUCTOR + // ######################################################################## + + /** + * The class constructor, called by CAS_Client::SetPGTStorageFile(). + * + * @param CAS_Client $cas_parent the CAS_Client instance that creates the object. + * @param string $path the path where the PGT's should be stored + * + * @return void + * + * @public + */ + function __construct($cas_parent,$path) + { + phpCAS::traceBegin(); + // call the ancestor's constructor + parent::__construct($cas_parent); + + if (empty($path)) { + $path = CAS_PGT_STORAGE_FILE_DEFAULT_PATH; + } + // check that the path is an absolute path + if (getenv("OS")=="Windows_NT" || strtoupper(substr(PHP_OS,0,3)) == 'WIN') { + + if (!preg_match('`^[a-zA-Z]:`', $path)) { + phpCAS::error('an absolute path is needed for PGT storage to file'); + } + + } else { + + if ( $path[0] != '/' ) { + phpCAS::error('an absolute path is needed for PGT storage to file'); + } + + // store the path (with a leading and trailing '/') + $path = preg_replace('|[/]*$|', '/', $path); + $path = preg_replace('|^[/]*|', '/', $path); + } + + $this->_path = $path; + phpCAS::traceEnd(); + } + + // ######################################################################## + // INITIALIZATION + // ######################################################################## + + /** + * This method is used to initialize the storage. Halts on error. + * + * @return void + * @public + */ + function init() + { + phpCAS::traceBegin(); + // if the storage has already been initialized, return immediatly + if ($this->isInitialized()) { + return; + } + // call the ancestor's method (mark as initialized) + parent::init(); + phpCAS::traceEnd(); + } + + // ######################################################################## + // PGT I/O + // ######################################################################## + + /** + * This method returns the filename corresponding to a PGT Iou. + * + * @param string $pgt_iou the PGT iou. + * + * @return a filename + * @private + */ + function getPGTIouFilename($pgt_iou) + { + phpCAS::traceBegin(); + $filename = $this->getPath()."phpcas-".hash("sha256", $pgt_iou); +// $filename = $this->getPath().$pgt_iou.'.plain'; + phpCAS::trace("Sha256 filename:" . $filename); + phpCAS::traceEnd(); + return $filename; + } + + /** + * This method stores a PGT and its corresponding PGT Iou into a file. Echoes a + * warning on error. + * + * @param string $pgt the PGT + * @param string $pgt_iou the PGT iou + * + * @return void + * + * @public + */ + function write($pgt,$pgt_iou) + { + phpCAS::traceBegin(); + $fname = $this->getPGTIouFilename($pgt_iou); + if (!file_exists($fname)) { + touch($fname); + // Chmod will fail on windows + @chmod($fname, 0600); + if ($f=fopen($fname, "w")) { + if (fputs($f, $pgt) === false) { + phpCAS::error('could not write PGT to `'.$fname.'\''); + } + phpCAS::trace('Successful write of PGT to `'.$fname.'\''); + fclose($f); + } else { + phpCAS::error('could not open `'.$fname.'\''); + } + } else { + phpCAS::error('File exists: `'.$fname.'\''); + } + phpCAS::traceEnd(); + } + + /** + * This method reads a PGT corresponding to a PGT Iou and deletes the + * corresponding file. + * + * @param string $pgt_iou the PGT iou + * + * @return the corresponding PGT, or FALSE on error + * + * @public + */ + function read($pgt_iou) + { + phpCAS::traceBegin(); + $pgt = false; + $fname = $this->getPGTIouFilename($pgt_iou); + if (file_exists($fname)) { + if (!($f=fopen($fname, "r"))) { + phpCAS::error('could not open `'.$fname.'\''); + } else { + if (($pgt=fgets($f)) === false) { + phpCAS::error('could not read PGT from `'.$fname.'\''); + } + phpCAS::trace('Successful read of PGT to `'.$fname.'\''); + fclose($f); + } + // delete the PGT file + @unlink($fname); + } else { + phpCAS::error('No such file `'.$fname.'\''); + } + phpCAS::traceEnd($pgt); + return $pgt; + } + + /** @} */ + +} +?> diff --git a/phpCas/source/CAS/ProxiedService.php b/phpCas/source/CAS/ProxiedService.php new file mode 100644 index 0000000..d70ca9c --- /dev/null +++ b/phpCas/source/CAS/ProxiedService.php @@ -0,0 +1,72 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines methods that allow proxy-authenticated service handlers + * to interact with phpCAS. + * + * Proxy service handlers must implement this interface as well as call + * phpCAS::initializeProxiedService($this) at some point in their implementation. + * + * While not required, proxy-authenticated service handlers are encouraged to + * implement the CAS_ProxiedService_Testable interface to facilitate unit testing. + * + * @class CAS_ProxiedService + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_ProxiedService +{ + + /** + * Answer a service identifier (URL) for whom we should fetch a proxy ticket. + * + * @return string + * @throws Exception If no service url is available. + */ + public function getServiceUrl (); + + /** + * Register a proxy ticket with the ProxiedService that it can use when + * making requests. + * + * @param string $proxyTicket Proxy ticket string + * + * @return void + * @throws InvalidArgumentException If the $proxyTicket is invalid. + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setProxyTicket ($proxyTicket); + +} +?> diff --git a/phpCas/source/CAS/ProxiedService/Abstract.php b/phpCas/source/CAS/ProxiedService/Abstract.php new file mode 100644 index 0000000..fade9e7 --- /dev/null +++ b/phpCas/source/CAS/ProxiedService/Abstract.php @@ -0,0 +1,149 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class implements common methods for ProxiedService implementations included + * with phpCAS. + * + * @class CAS_ProxiedService_Abstract + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +abstract class CAS_ProxiedService_Abstract +implements CAS_ProxiedService, CAS_ProxiedService_Testable +{ + + /** + * The proxy ticket that can be used when making service requests. + * @var string $_proxyTicket; + */ + private $_proxyTicket; + + /** + * Register a proxy ticket with the Proxy that it can use when making requests. + * + * @param string $proxyTicket proxy ticket + * + * @return void + * @throws InvalidArgumentException If the $proxyTicket is invalid. + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setProxyTicket ($proxyTicket) + { + if (empty($proxyTicket)) { + throw new CAS_InvalidArgumentException( + 'Trying to initialize with an empty proxy ticket.' + ); + } + if (!empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'Already initialized, cannot change the proxy ticket.' + ); + } + $this->_proxyTicket = $proxyTicket; + } + + /** + * Answer the proxy ticket to be used when making requests. + * + * @return string + * @throws CAS_OutOfSequenceException If called before a proxy ticket has + * already been initialized/set. + */ + protected function getProxyTicket () + { + if (empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'No proxy ticket yet. Call $this->initializeProxyTicket() to aquire the proxy ticket.' + ); + } + + return $this->_proxyTicket; + } + + /** + * @var CAS_Client $_casClient; + */ + private $_casClient; + + /** + * Use a particular CAS_Client->initializeProxiedService() rather than the + * static phpCAS::initializeProxiedService(). + * + * This method should not be called in standard operation, but is needed for unit + * testing. + * + * @param CAS_Client $casClient cas client + * + * @return void + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setCasClient (CAS_Client $casClient) + { + if (!empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'Already initialized, cannot change the CAS_Client.' + ); + } + + $this->_casClient = $casClient; + } + + /** + * Fetch our proxy ticket. + * + * Descendent classes should call this method once their service URL is available + * to initialize their proxy ticket. + * + * @return void + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized. + */ + protected function initializeProxyTicket() + { + if (!empty($this->_proxyTicket)) { + throw new CAS_OutOfSequenceException( + 'Already initialized, cannot initialize again.' + ); + } + // Allow usage of a particular CAS_Client for unit testing. + if (empty($this->_casClient)) { + phpCAS::initializeProxiedService($this); + } else { + $this->_casClient->initializeProxiedService($this); + } + } + +} +?> diff --git a/phpCas/source/CAS/ProxiedService/Exception.php b/phpCas/source/CAS/ProxiedService/Exception.php new file mode 100644 index 0000000..5a1e696 --- /dev/null +++ b/phpCas/source/CAS/ProxiedService/Exception.php @@ -0,0 +1,46 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An Exception for problems communicating with a proxied service. + * + * @class CAS_ProxiedService_Exception + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Exception +extends Exception +implements CAS_Exception +{ + +} +?> diff --git a/phpCas/source/CAS/ProxiedService/Http.php b/phpCas/source/CAS/ProxiedService/Http.php new file mode 100644 index 0000000..7c9824f --- /dev/null +++ b/phpCas/source/CAS/ProxiedService/Http.php @@ -0,0 +1,91 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines methods that clients should use for configuring, sending, + * and receiving proxied HTTP requests. + * + * @class CAS_ProxiedService_Http + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_ProxiedService_Http +{ + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url Url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl ($url); + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send (); + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders (); + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody (); + +} +?> diff --git a/phpCas/source/CAS/ProxiedService/Http/Abstract.php b/phpCas/source/CAS/ProxiedService/Http/Abstract.php new file mode 100644 index 0000000..abeddf8 --- /dev/null +++ b/phpCas/source/CAS/ProxiedService/Http/Abstract.php @@ -0,0 +1,360 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class implements common methods for ProxiedService implementations included + * with phpCAS. + * + * @class CAS_ProxiedService_Http_Abstract + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +abstract class CAS_ProxiedService_Http_Abstract extends +CAS_ProxiedService_Abstract implements CAS_ProxiedService_Http +{ + /** + * The HTTP request mechanism talking to the target service. + * + * @var CAS_Request_RequestInterface $requestHandler + */ + protected $requestHandler; + + /** + * The storage mechanism for cookies set by the target service. + * + * @var CAS_CookieJar $_cookieJar + */ + private $_cookieJar; + + /** + * Constructor. + * + * @param CAS_Request_RequestInterface $requestHandler request handler object + * @param CAS_CookieJar $cookieJar cookieJar object + * + * @return void + */ + public function __construct(CAS_Request_RequestInterface $requestHandler, + CAS_CookieJar $cookieJar + ) { + $this->requestHandler = $requestHandler; + $this->_cookieJar = $cookieJar; + } + + /** + * The target service url. + * @var string $_url; + */ + private $_url; + + /** + * Answer a service identifier (URL) for whom we should fetch a proxy ticket. + * + * @return string + * @throws Exception If no service url is available. + */ + public function getServiceUrl() + { + if (empty($this->_url)) { + throw new CAS_ProxiedService_Exception( + 'No URL set via ' . get_class($this) . '->setUrl($url).' + ); + } + + return $this->_url; + } + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl($url) + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the URL, request already sent.' + ); + } + if (!is_string($url)) { + throw new CAS_InvalidArgumentException('$url must be a string.'); + } + + $this->_url = $url; + } + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return void + * @throws CAS_OutOfSequenceException If called multiple times. + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ + public function send() + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot send, request already sent.' + ); + } + + phpCAS::traceBegin(); + + // Get our proxy ticket and append it to our URL. + $this->initializeProxyTicket(); + $url = $this->getServiceUrl(); + if (strstr($url, '?') === false) { + $url = $url . '?ticket=' . $this->getProxyTicket(); + } else { + $url = $url . '&ticket=' . $this->getProxyTicket(); + } + + try { + $this->makeRequest($url); + } catch (Exception $e) { + phpCAS::traceEnd(); + throw $e; + } + } + + /** + * Indicator of the number of requests (including redirects performed. + * + * @var int $_numRequests; + */ + private $_numRequests = 0; + + /** + * The response headers. + * + * @var array $_responseHeaders; + */ + private $_responseHeaders = array(); + + /** + * The response status code. + * + * @var string $_responseStatusCode; + */ + private $_responseStatusCode = ''; + + /** + * The response headers. + * + * @var string $_responseBody; + */ + private $_responseBody = ''; + + /** + * Build and perform a request, following redirects + * + * @param string $url url for the request + * + * @return void + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ + protected function makeRequest($url) + { + // Verify that we are not in a redirect loop + $this->_numRequests++; + if ($this->_numRequests > 4) { + $message = 'Exceeded the maximum number of redirects (3) in proxied service request.'; + phpCAS::trace($message); + throw new CAS_ProxiedService_Exception($message); + } + + // Create a new request. + $request = clone $this->requestHandler; + $request->setUrl($url); + + // Add any cookies to the request. + $request->addCookies($this->_cookieJar->getCookies($url)); + + // Add any other parts of the request needed by concrete classes + $this->populateRequest($request); + + // Perform the request. + phpCAS::trace('Performing proxied service request to \'' . $url . '\''); + if (!$request->send()) { + $message = 'Could not perform proxied service request to URL`' + . $url . '\'. ' . $request->getErrorMessage(); + phpCAS::trace($message); + throw new CAS_ProxiedService_Exception($message); + } + + // Store any cookies from the response; + $this->_cookieJar->storeCookies($url, $request->getResponseHeaders()); + + // Follow any redirects + if ($redirectUrl = $this->getRedirectUrl($request->getResponseHeaders()) + ) { + phpCAS::trace('Found redirect:' . $redirectUrl); + $this->makeRequest($redirectUrl); + } else { + + $this->_responseHeaders = $request->getResponseHeaders(); + $this->_responseBody = $request->getResponseBody(); + $this->_responseStatusCode = $request->getResponseStatusCode(); + } + } + + /** + * Add any other parts of the request needed by concrete classes + * + * @param CAS_Request_RequestInterface $request request interface object + * + * @return void + */ + abstract protected function populateRequest( + CAS_Request_RequestInterface $request + ); + + /** + * Answer a redirect URL if a redirect header is found, otherwise null. + * + * @param array $responseHeaders response header to extract a redirect from + * + * @return string or null + */ + protected function getRedirectUrl(array $responseHeaders) + { + // Check for the redirect after authentication + foreach ($responseHeaders as $header) { + if ( preg_match('/^(Location:|URI:)\s*([^\s]+.*)$/', $header, $matches) + ) { + return trim(array_pop($matches)); + } + } + return null; + } + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer true if our request has been sent yet. + * + * @return bool + */ + protected function hasBeenSent() + { + return ($this->_numRequests > 0); + } + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders() + { + if (!$this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot access response, request not sent yet.' + ); + } + + return $this->_responseHeaders; + } + + /** + * Answer HTTP status code of the response + * + * @return int + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseStatusCode() + { + if (!$this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot access response, request not sent yet.' + ); + } + + return $this->_responseStatusCode; + } + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody() + { + if (!$this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot access response, request not sent yet.' + ); + } + + return $this->_responseBody; + } + + /** + * Answer the cookies from the response. This may include cookies set during + * redirect responses. + * + * @return array An array containing cookies. E.g. array('name' => 'val'); + */ + public function getCookies() + { + return $this->_cookieJar->getCookies($this->getServiceUrl()); + } + +} +?> diff --git a/phpCas/source/CAS/ProxiedService/Http/Get.php b/phpCas/source/CAS/ProxiedService/Http/Get.php new file mode 100644 index 0000000..78e35de --- /dev/null +++ b/phpCas/source/CAS/ProxiedService/Http/Get.php @@ -0,0 +1,85 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class is used to make proxied service requests via the HTTP GET method. + * + * Usage Example: + * + * try { + * $service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET); + * $service->setUrl('http://www.example.com/path/'); + * $service->send(); + * if ($service->getResponseStatusCode() == 200) + * return $service->getResponseBody(); + * else + * // The service responded with an error code 404, 500, etc. + * throw new Exception('The service responded with an error.'); + * + * } catch (CAS_ProxyTicketException $e) { + * if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE) + * return "Your login has timed out. You need to log in again."; + * else + * // Other proxy ticket errors are from bad request format + * // (shouldn't happen) or CAS server failure (unlikely) + * // so lets just stop if we hit those. + * throw $e; + * } catch (CAS_ProxiedService_Exception $e) { + * // Something prevented the service request from being sent or received. + * // We didn't even get a valid error response (404, 500, etc), so this + * // might be caused by a network error or a DNS resolution failure. + * // We could handle it in some way, but for now we will just stop. + * throw $e; + * } + * + * @class CAS_ProxiedService_Http_Get + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Http_Get +extends CAS_ProxiedService_Http_Abstract +{ + + /** + * Add any other parts of the request needed by concrete classes + * + * @param CAS_Request_RequestInterface $request request interface + * + * @return void + */ + protected function populateRequest (CAS_Request_RequestInterface $request) + { + // do nothing, since the URL has already been sent and that is our + // only data. + } +} +?> diff --git a/phpCas/source/CAS/ProxiedService/Http/Post.php b/phpCas/source/CAS/ProxiedService/Http/Post.php new file mode 100644 index 0000000..7d4ecd3 --- /dev/null +++ b/phpCas/source/CAS/ProxiedService/Http/Post.php @@ -0,0 +1,152 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This class is used to make proxied service requests via the HTTP POST method. + * + * Usage Example: + * + * try { + * $service = phpCAS::getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_POST); + * $service->setUrl('http://www.example.com/path/'); + * $service->setContentType('text/xml'); + * $service->setBody('example.search'); + * $service->send(); + * if ($service->getResponseStatusCode() == 200) + * return $service->getResponseBody(); + * else + * // The service responded with an error code 404, 500, etc. + * throw new Exception('The service responded with an error.'); + * + * } catch (CAS_ProxyTicketException $e) { + * if ($e->getCode() == PHPCAS_SERVICE_PT_FAILURE) + * return "Your login has timed out. You need to log in again."; + * else + * // Other proxy ticket errors are from bad request format + * // (shouldn't happen) or CAS server failure (unlikely) so lets just + * // stop if we hit those. + * throw $e; + * } catch (CAS_ProxiedService_Exception $e) { + * // Something prevented the service request from being sent or received. + * // We didn't even get a valid error response (404, 500, etc), so this + * // might be caused by a network error or a DNS resolution failure. + * // We could handle it in some way, but for now we will just stop. + * throw $e; + * } + * + * @class CAS_ProxiedService_Http_Post + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Http_Post +extends CAS_ProxiedService_Http_Abstract +{ + + /** + * The content-type of this request + * + * @var string $_contentType + */ + private $_contentType; + + /** + * The body of the this request + * + * @var string $_body + */ + private $_body; + + /** + * Set the content type of this POST request. + * + * @param string $contentType content type + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setContentType ($contentType) + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the content type, request already sent.' + ); + } + + $this->_contentType = $contentType; + } + + /** + * Set the body of this POST request. + * + * @param string $body body to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setBody ($body) + { + if ($this->hasBeenSent()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the body, request already sent.' + ); + } + + $this->_body = $body; + } + + /** + * Add any other parts of the request needed by concrete classes + * + * @param CAS_Request_RequestInterface $request request interface class + * + * @return void + */ + protected function populateRequest (CAS_Request_RequestInterface $request) + { + if (empty($this->_contentType) && !empty($this->_body)) { + throw new CAS_ProxiedService_Exception( + "If you pass a POST body, you must specify a content type via " + .get_class($this).'->setContentType($contentType).' + ); + } + + $request->makePost(); + if (!empty($this->_body)) { + $request->addHeader('Content-Type: '.$this->_contentType); + $request->addHeader('Content-Length: '.strlen($this->_body)); + $request->setPostBody($this->_body); + } + } + + +} +?> diff --git a/phpCas/source/CAS/ProxiedService/Imap.php b/phpCas/source/CAS/ProxiedService/Imap.php new file mode 100644 index 0000000..847da28 --- /dev/null +++ b/phpCas/source/CAS/ProxiedService/Imap.php @@ -0,0 +1,280 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Provides access to a proxy-authenticated IMAP stream + * + * @class CAS_ProxiedService_Imap + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxiedService_Imap +extends CAS_ProxiedService_Abstract +{ + + /** + * The username to send via imap_open. + * + * @var string $_username; + */ + private $_username; + + /** + * Constructor. + * + * @param string $username Username + * + * @return void + */ + public function __construct ($username) + { + if (!is_string($username) || !strlen($username)) { + throw new CAS_InvalidArgumentException('Invalid username.'); + } + + $this->_username = $username; + } + + /** + * The target service url. + * @var string $_url; + */ + private $_url; + + /** + * Answer a service identifier (URL) for whom we should fetch a proxy ticket. + * + * @return string + * @throws Exception If no service url is available. + */ + public function getServiceUrl () + { + if (empty($this->_url)) { + throw new CAS_ProxiedService_Exception( + 'No URL set via '.get_class($this).'->getServiceUrl($url).' + ); + } + + return $this->_url; + } + + /********************************************************* + * Configure the Stream + *********************************************************/ + + /** + * Set the URL of the service to pass to CAS for proxy-ticket retrieval. + * + * @param string $url Url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the stream has been opened. + */ + public function setServiceUrl ($url) + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the URL, stream already opened.' + ); + } + if (!is_string($url) || !strlen($url)) { + throw new CAS_InvalidArgumentException('Invalid url.'); + } + + $this->_url = $url; + } + + /** + * The mailbox to open. See the $mailbox parameter of imap_open(). + * + * @var string $_mailbox + */ + private $_mailbox; + + /** + * Set the mailbox to open. See the $mailbox parameter of imap_open(). + * + * @param string $mailbox Mailbox to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the stream has been opened. + */ + public function setMailbox ($mailbox) + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot set the mailbox, stream already opened.' + ); + } + if (!is_string($mailbox) || !strlen($mailbox)) { + throw new CAS_InvalidArgumentException('Invalid mailbox.'); + } + + $this->_mailbox = $mailbox; + } + + /** + * A bit mask of options to pass to imap_open() as the $options parameter. + * + * @var int $_options + */ + private $_options = null; + + /** + * Set the options for opening the stream. See the $options parameter of + * imap_open(). + * + * @param int $options Options for the stream + * + * @return void + * @throws CAS_OutOfSequenceException If called after the stream has been opened. + */ + public function setOptions ($options) + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot set options, stream already opened.' + ); + } + if (!is_int($options)) { + throw new CAS_InvalidArgumentException('Invalid options.'); + } + + $this->_options = $options; + } + + /********************************************************* + * 2. Open the stream + *********************************************************/ + + /** + * Open the IMAP stream (similar to imap_open()). + * + * @return resource Returns an IMAP stream on success + * @throws CAS_OutOfSequenceException If called multiple times. + * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. + * The code of the Exception will be one of: + * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE + * PHPCAS_SERVICE_PT_FAILURE + * @throws CAS_ProxiedService_Exception If there is a failure sending the + * request to the target service. + */ + public function open () + { + if ($this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException('Stream already opened.'); + } + if (empty($this->_mailbox)) { + throw new CAS_ProxiedService_Exception( + 'You must specify a mailbox via '.get_class($this) + .'->setMailbox($mailbox)' + ); + } + + phpCAS::traceBegin(); + + // Get our proxy ticket and append it to our URL. + $this->initializeProxyTicket(); + phpCAS::trace('opening IMAP mailbox `'.$this->_mailbox.'\'...'); + $this->_stream = @imap_open( + $this->_mailbox, $this->_username, $this->getProxyTicket(), + $this->_options + ); + if ($this->_stream) { + phpCAS::trace('ok'); + } else { + phpCAS::trace('could not open mailbox'); + // @todo add localization integration. + $message = 'IMAP Error: '.$this->_url.' '. var_export(imap_errors(), true); + phpCAS::trace($message); + throw new CAS_ProxiedService_Exception($message); + } + + phpCAS::traceEnd(); + return $this->_stream; + } + + /** + * Answer true if our request has been sent yet. + * + * @return bool + */ + protected function hasBeenOpened () + { + return !empty($this->_stream); + } + + /********************************************************* + * 3. Access the result + *********************************************************/ + /** + * The IMAP stream + * + * @var resource $_stream + */ + private $_stream; + + /** + * Answer the IMAP stream + * + * @return resource + */ + public function getStream () + { + if (!$this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot access stream, not opened yet.' + ); + } + return $this->_stream; + } + + /** + * CAS_Client::serviceMail() needs to return the proxy ticket for some reason, + * so this method provides access to it. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the stream has been + * opened. + */ + public function getImapProxyTicket () + { + if (!$this->hasBeenOpened()) { + throw new CAS_OutOfSequenceException( + 'Cannot access errors, stream not opened yet.' + ); + } + return $this->getProxyTicket(); + } +} +?> diff --git a/phpCas/source/CAS/ProxiedService/Testable.php b/phpCas/source/CAS/ProxiedService/Testable.php new file mode 100644 index 0000000..51f0767 --- /dev/null +++ b/phpCas/source/CAS/ProxiedService/Testable.php @@ -0,0 +1,75 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines methods that allow proxy-authenticated service handlers + * to be tested in unit tests. + * + * Classes implementing this interface SHOULD store the CAS_Client passed and + * initialize themselves with that client rather than via the static phpCAS + * method. For example: + * + * / ** + * * Fetch our proxy ticket. + * * / + * protected function initializeProxyTicket() { + * // Allow usage of a particular CAS_Client for unit testing. + * if (is_null($this->casClient)) + * phpCAS::initializeProxiedService($this); + * else + * $this->casClient->initializeProxiedService($this); + * } + * + * @class CAS_ProxiedService_Testabel + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_ProxiedService_Testable +{ + + /** + * Use a particular CAS_Client->initializeProxiedService() rather than the + * static phpCAS::initializeProxiedService(). + * + * This method should not be called in standard operation, but is needed for unit + * testing. + * + * @param CAS_Client $casClient Cas client object + * + * @return void + * @throws CAS_OutOfSequenceException If called after a proxy ticket has + * already been initialized/set. + */ + public function setCasClient (CAS_Client $casClient); + +} +?> diff --git a/phpCas/source/CAS/ProxyChain.php b/phpCas/source/CAS/ProxyChain.php new file mode 100644 index 0000000..2594d14 --- /dev/null +++ b/phpCas/source/CAS/ProxyChain.php @@ -0,0 +1,127 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * A normal proxy-chain definition that lists each level of the chain as either + * a string or regular expression. + * + * @class CAS_ProxyChain + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_ProxyChain +implements CAS_ProxyChain_Interface +{ + + protected $chain = array(); + + /** + * A chain is an array of strings or regexp strings that will be matched + * against. Regexp will be matched with preg_match and strings will be + * matched from the beginning. A string must fully match the beginning of + * an proxy url. So you can define a full domain as acceptable or go further + * down. + * Proxies have to be defined in reverse from the service to the user. If a + * user hits service A get proxied via B to service C the list of acceptable + * proxies on C would be array(B,A); + * + * @param array $chain A chain of proxies + */ + public function __construct(array $chain) + { + // Ensure that we have an indexed array + $this->chain = array_values($chain); + } + + /** + * Match a list of proxies. + * + * @param array $list The list of proxies in front of this service. + * + * @return bool + */ + public function matches(array $list) + { + $list = array_values($list); // Ensure that we have an indexed array + if ($this->isSizeValid($list)) { + $mismatch = false; + foreach ($this->chain as $i => $search) { + $proxy_url = $list[$i]; + if (preg_match('/^\/.*\/[ixASUXu]*$/s', $search)) { + if (preg_match($search, $proxy_url)) { + phpCAS::trace( + "Found regexp " . $search . " matching " . $proxy_url + ); + } else { + phpCAS::trace( + "No regexp match " . $search . " != " . $proxy_url + ); + $mismatch = true; + break; + } + } else { + if (strncasecmp($search, $proxy_url, strlen($search)) == 0) { + phpCAS::trace( + "Found string " . $search . " matching " . $proxy_url + ); + } else { + phpCAS::trace( + "No match " . $search . " != " . $proxy_url + ); + $mismatch = true; + break; + } + } + } + if (!$mismatch) { + phpCAS::trace("Proxy chain matches"); + return true; + } + } else { + phpCAS::trace("Proxy chain skipped: size mismatch"); + } + return false; + } + + /** + * Validate the size of the the list as compared to our chain. + * + * @param array $list List of proxies + * + * @return bool + */ + protected function isSizeValid (array $list) + { + return (sizeof($this->chain) == sizeof($list)); + } +} diff --git a/phpCas/source/CAS/ProxyChain/AllowedList.php b/phpCas/source/CAS/ProxyChain/AllowedList.php new file mode 100644 index 0000000..62d196a --- /dev/null +++ b/phpCas/source/CAS/ProxyChain/AllowedList.php @@ -0,0 +1,119 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + + +/** + * ProxyChain is a container for storing chains of valid proxies that can + * be used to validate proxied requests to a service + * + * @class CAS_ProxyChain_AllowedList + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +class CAS_ProxyChain_AllowedList +{ + + private $_chains = array(); + + /** + * Check whether proxies are allowed by configuration + * + * @return bool + */ + public function isProxyingAllowed() + { + return (count($this->_chains) > 0); + } + + /** + * Add a chain of proxies to the list of possible chains + * + * @param CAS_ProxyChain_Interface $chain A chain of proxies + * + * @return void + */ + public function allowProxyChain(CAS_ProxyChain_Interface $chain) + { + $this->_chains[] = $chain; + } + + /** + * Check if the proxies found in the response match the allowed proxies + * + * @param array $proxies list of proxies to check + * + * @return bool whether the proxies match the allowed proxies + */ + public function isProxyListAllowed(array $proxies) + { + phpCAS::traceBegin(); + if (empty($proxies)) { + phpCAS::trace("No proxies were found in the response"); + phpCAS::traceEnd(true); + return true; + } elseif (!$this->isProxyingAllowed()) { + phpCAS::trace("Proxies are not allowed"); + phpCAS::traceEnd(false); + return false; + } else { + $res = $this->contains($proxies); + phpCAS::traceEnd($res); + return $res; + } + } + + /** + * Validate the proxies from the proxy ticket validation against the + * chains that were definded. + * + * @param array $list List of proxies from the proxy ticket validation. + * + * @return if any chain fully matches the supplied list + */ + public function contains(array $list) + { + phpCAS::traceBegin(); + $count = 0; + foreach ($this->_chains as $chain) { + phpCAS::trace("Checking chain ". $count++); + if ($chain->matches($list)) { + phpCAS::traceEnd(true); + return true; + } + } + phpCAS::trace("No proxy chain matches."); + phpCAS::traceEnd(false); + return false; + } +} +?> diff --git a/phpCas/source/CAS/ProxyChain/Any.php b/phpCas/source/CAS/ProxyChain/Any.php new file mode 100644 index 0000000..0cd92f7 --- /dev/null +++ b/phpCas/source/CAS/ProxyChain/Any.php @@ -0,0 +1,64 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * A proxy-chain definition that will match any list of proxies. + * + * Use this class for quick testing or in certain production screnarios you + * might want to allow allow any other valid service to proxy your service. + * + * THIS CLASS IS HOWEVER NOT RECOMMENDED FOR PRODUCTION AND HAS SECURITY + * IMPLICATIONS: YOU ARE ALLOWING ANY SERVICE TO ACT ON BEHALF OF A USER + * ON THIS SERVICE. + * + * @class CAS_ProxyChain_Any + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxyChain_Any +implements CAS_ProxyChain_Interface +{ + + /** + * Match a list of proxies. + * + * @param array $list The list of proxies in front of this service. + * + * @return bool + */ + public function matches(array $list) + { + phpCAS::trace("Using CAS_ProxyChain_Any. No proxy validation is performed."); + return true; + } + +} diff --git a/phpCas/source/CAS/ProxyChain/Interface.php b/phpCas/source/CAS/ProxyChain/Interface.php new file mode 100644 index 0000000..d247115 --- /dev/null +++ b/phpCas/source/CAS/ProxyChain/Interface.php @@ -0,0 +1,53 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An interface for classes that define a list of allowed proxies in front of + * the current application. + * + * @class CAS_ProxyChain_Interface + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_ProxyChain_Interface +{ + + /** + * Match a list of proxies. + * + * @param array $list The list of proxies in front of this service. + * + * @return bool + */ + public function matches(array $list); + +} \ No newline at end of file diff --git a/phpCas/source/CAS/ProxyChain/Trusted.php b/phpCas/source/CAS/ProxyChain/Trusted.php new file mode 100644 index 0000000..7fa6129 --- /dev/null +++ b/phpCas/source/CAS/ProxyChain/Trusted.php @@ -0,0 +1,59 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * A proxy-chain definition that defines a chain up to a trusted proxy and + * delegates the resposibility of validating the rest of the chain to that + * trusted proxy. + * + * @class CAS_ProxyChain_Trusted + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxyChain_Trusted +extends CAS_ProxyChain +implements CAS_ProxyChain_Interface +{ + + /** + * Validate the size of the the list as compared to our chain. + * + * @param array $list list of proxies + * + * @return bool + */ + protected function isSizeValid (array $list) + { + return (sizeof($this->chain) <= sizeof($list)); + } + +} diff --git a/phpCas/source/CAS/ProxyTicketException.php b/phpCas/source/CAS/ProxyTicketException.php new file mode 100644 index 0000000..7233046 --- /dev/null +++ b/phpCas/source/CAS/ProxyTicketException.php @@ -0,0 +1,71 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + * + */ + +/** + * An Exception for errors related to fetching or validating proxy tickets. + * + * @class CAS_ProxyTicketException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_ProxyTicketException +extends BadMethodCallException +implements CAS_Exception +{ + + /** + * Constructor + * + * @param string $message Message text + * @param int $code Error code + * + * @return void + */ + public function __construct ($message, $code = PHPCAS_SERVICE_PT_FAILURE) + { + // Warn if the code is not in our allowed list + $ptCodes = array( + PHPCAS_SERVICE_PT_FAILURE, + PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, + PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, + ); + if (!in_array($code, $ptCodes)) { + trigger_error( + 'Invalid code '.$code + .' passed. Must be one of PHPCAS_SERVICE_PT_FAILURE, PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE, or PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE.' + ); + } + + parent::__construct($message, $code); + } +} diff --git a/phpCas/source/CAS/Request/AbstractRequest.php b/phpCas/source/CAS/Request/AbstractRequest.php new file mode 100644 index 0000000..f3dd28b --- /dev/null +++ b/phpCas/source/CAS/Request/AbstractRequest.php @@ -0,0 +1,379 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Provides support for performing web-requests via curl + * + * @class CAS_Request_AbstractRequest + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +abstract class CAS_Request_AbstractRequest +implements CAS_Request_RequestInterface +{ + + protected $url = null; + protected $cookies = array(); + protected $headers = array(); + protected $isPost = false; + protected $postBody = null; + protected $caCertPath = null; + protected $validateCN = true; + private $_sent = false; + private $_responseHeaders = array(); + private $_responseBody = null; + private $_errorMessage = ''; + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url Url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl ($url) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->url = $url; + } + + /** + * Add a cookie to the request. + * + * @param string $name Name of entry + * @param string $value value of entry + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookie ($name, $value) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->cookies[$name] = $value; + } + + /** + * Add an array of cookies to the request. + * The cookie array is of the form + * array('cookie_name' => 'cookie_value', 'cookie_name2' => cookie_value2') + * + * @param array $cookies cookies to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookies (array $cookies) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->cookies = array_merge($this->cookies, $cookies); + } + + /** + * Add a header string to the request. + * + * @param string $header Header to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeader ($header) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->headers[] = $header; + } + + /** + * Add an array of header strings to the request. + * + * @param array $headers headers to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeaders (array $headers) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->headers = array_merge($this->headers, $headers); + } + + /** + * Make the request a POST request rather than the default GET request. + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function makePost () + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + + $this->isPost = true; + } + + /** + * Add a POST body to the request + * + * @param string $body body to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setPostBody ($body) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + if (!$this->isPost) { + throw new CAS_OutOfSequenceException( + 'Cannot add a POST body to a GET request, use makePost() first.' + ); + } + + $this->postBody = $body; + } + + /** + * Specify the path to an SSL CA certificate to validate the server with. + * + * @param string $caCertPath path to cert + * @param bool $validate_cn valdiate CN of certificate + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setSslCaCert ($caCertPath,$validate_cn=true) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + $this->caCertPath = $caCertPath; + $this->validateCN = $validate_cn; + } + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send () + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot send again.' + ); + } + if (is_null($this->url) || !$this->url) { + throw new CAS_OutOfSequenceException( + 'A url must be specified via setUrl() before the request can be sent.' + ); + } + $this->_sent = true; + return $this->sendRequest(); + } + + /** + * Send the request and store the results. + * + * @return bool TRUE on success, FALSE on failure. + */ + abstract protected function sendRequest (); + + /** + * Store the response headers. + * + * @param array $headers headers to store + * + * @return void + */ + protected function storeResponseHeaders (array $headers) + { + $this->_responseHeaders = array_merge($this->_responseHeaders, $headers); + } + + /** + * Store a single response header to our array. + * + * @param string $header header to store + * + * @return void + */ + protected function storeResponseHeader ($header) + { + $this->_responseHeaders[] = $header; + } + + /** + * Store the response body. + * + * @param string $body body to store + * + * @return void + */ + protected function storeResponseBody ($body) + { + $this->_responseBody = $body; + } + + /** + * Add a string to our error message. + * + * @param string $message message to add + * + * @return void + */ + protected function storeErrorMessage ($message) + { + $this->_errorMessage .= $message; + } + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders () + { + if (!$this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has not been sent yet. Cannot '.__METHOD__ + ); + } + return $this->_responseHeaders; + } + + /** + * Answer HTTP status code of the response + * + * @return int + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseStatusCode () + { + if (!$this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has not been sent yet. Cannot '.__METHOD__ + ); + } + + if (!preg_match( + '/HTTP\/[0-9.]+\s+([0-9]+)\s*(.*)/', + $this->_responseHeaders[0], $matches + ) + ) { + throw new CAS_Request_Exception( + 'Bad response, no status code was found in the first line.' + ); + } + + return intval($matches[1]); + } + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody () + { + if (!$this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has not been sent yet. Cannot '.__METHOD__ + ); + } + + return $this->_responseBody; + } + + /** + * Answer a message describing any errors if the request failed. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getErrorMessage () + { + if (!$this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has not been sent yet. Cannot '.__METHOD__ + ); + } + return $this->_errorMessage; + } +} diff --git a/phpCas/source/CAS/Request/CurlMultiRequest.php b/phpCas/source/CAS/Request/CurlMultiRequest.php new file mode 100644 index 0000000..7099608 --- /dev/null +++ b/phpCas/source/CAS/Request/CurlMultiRequest.php @@ -0,0 +1,146 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines a class library for performing multiple web requests + * in batches. Implementations of this interface may perform requests serially + * or in parallel. + * + * @class CAS_Request_CurlMultiRequest + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_Request_CurlMultiRequest +implements CAS_Request_MultiRequestInterface +{ + private $_requests = array(); + private $_sent = false; + + /********************************************************* + * Add Requests + *********************************************************/ + + /** + * Add a new Request to this batch. + * Note, implementations will likely restrict requests to their own concrete + * class hierarchy. + * + * @param CAS_Request_RequestInterface $request reqest to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + * @throws CAS_InvalidArgumentException If passed a Request of the wrong + * implmentation. + */ + public function addRequest (CAS_Request_RequestInterface $request) + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + if (!$request instanceof CAS_Request_CurlRequest) { + throw new CAS_InvalidArgumentException( + 'As a CAS_Request_CurlMultiRequest, I can only work with CAS_Request_CurlRequest objects.' + ); + } + + $this->_requests[] = $request; + } + + /** + * Retrieve the number of requests added to this batch. + * + * @return number of request elements + */ + public function getNumRequests() + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot '.__METHOD__ + ); + } + return count($this->_requests); + } + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. After sending, all requests will have their + * responses poulated. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send () + { + if ($this->_sent) { + throw new CAS_OutOfSequenceException( + 'Request has already been sent cannot send again.' + ); + } + if (!count($this->_requests)) { + throw new CAS_OutOfSequenceException( + 'At least one request must be added via addRequest() before the multi-request can be sent.' + ); + } + + $this->_sent = true; + + // Initialize our handles and configure all requests. + $handles = array(); + $multiHandle = curl_multi_init(); + foreach ($this->_requests as $i => $request) { + $handle = $request->initAndConfigure(); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); + $handles[$i] = $handle; + curl_multi_add_handle($multiHandle, $handle); + } + + // Execute the requests in parallel. + do { + curl_multi_exec($multiHandle, $running); + } while ($running > 0); + + // Populate all of the responses or errors back into the request objects. + foreach ($this->_requests as $i => $request) { + $buf = curl_multi_getcontent($handles[$i]); + $request->_storeResponseBody($buf); + curl_multi_remove_handle($multiHandle, $handles[$i]); + curl_close($handles[$i]); + } + + curl_multi_close($multiHandle); + } +} diff --git a/phpCas/source/CAS/Request/CurlRequest.php b/phpCas/source/CAS/Request/CurlRequest.php new file mode 100644 index 0000000..86d2492 --- /dev/null +++ b/phpCas/source/CAS/Request/CurlRequest.php @@ -0,0 +1,200 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Provides support for performing web-requests via curl + * + * @class CAS_Request_CurlRequest + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_Request_CurlRequest +extends CAS_Request_AbstractRequest +implements CAS_Request_RequestInterface +{ + + /** + * Set additional curl options + * + * @param array $options option to set + * + * @return void + */ + public function setCurlOptions (array $options) + { + $this->_curlOptions = $options; + } + private $_curlOptions = array(); + + /** + * Send the request and store the results. + * + * @return bool true on success, false on failure. + */ + protected function sendRequest () + { + phpCAS::traceBegin(); + + /********************************************************* + * initialize the CURL session + *********************************************************/ + $ch = $this->initAndConfigure(); + + /********************************************************* + * Perform the query + *********************************************************/ + $buf = curl_exec($ch); + if ( $buf === false ) { + phpCAS::trace('curl_exec() failed'); + $this->storeErrorMessage( + 'CURL error #'.curl_errno($ch).': '.curl_error($ch) + ); + $res = false; + } else { + $this->storeResponseBody($buf); + phpCAS::trace("Response Body: \n".$buf."\n"); + $res = true; + + } + // close the CURL session + curl_close($ch); + + phpCAS::traceEnd($res); + return $res; + } + + /** + * Internal method to initialize our cURL handle and configure the request. + * This method should NOT be used outside of the CurlRequest or the + * CurlMultiRequest. + * + * @return resource The cURL handle on success, false on failure + */ + public function initAndConfigure() + { + /********************************************************* + * initialize the CURL session + *********************************************************/ + $ch = curl_init($this->url); + + if (version_compare(PHP_VERSION, '5.1.3', '>=')) { + //only avaible in php5 + curl_setopt_array($ch, $this->_curlOptions); + } else { + foreach ($this->_curlOptions as $key => $value) { + curl_setopt($ch, $key, $value); + } + } + + /********************************************************* + * Set SSL configuration + *********************************************************/ + if ($this->caCertPath) { + if ($this->validateCN) { + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); + } else { + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + } + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1); + curl_setopt($ch, CURLOPT_CAINFO, $this->caCertPath); + phpCAS::trace('CURL: Set CURLOPT_CAINFO ' . $this->caCertPath); + } else { + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + } + + /********************************************************* + * Configure curl to capture our output. + *********************************************************/ + // return the CURL output into a variable + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + + // get the HTTP header with a callback + curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, '_curlReadHeaders')); + + /********************************************************* + * Add cookie headers to our request. + *********************************************************/ + if (count($this->cookies)) { + $cookieStrings = array(); + foreach ($this->cookies as $name => $val) { + $cookieStrings[] = $name.'='.$val; + } + curl_setopt($ch, CURLOPT_COOKIE, implode(';', $cookieStrings)); + } + + /********************************************************* + * Add any additional headers + *********************************************************/ + if (count($this->headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $this->headers); + } + + /********************************************************* + * Flag and Body for POST requests + *********************************************************/ + if ($this->isPost) { + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $this->postBody); + } + + return $ch; + } + + /** + * Store the response body. + * This method should NOT be used outside of the CurlRequest or the + * CurlMultiRequest. + * + * @param string $body body to stor + * + * @return void + */ + private function _storeResponseBody ($body) + { + $this->storeResponseBody($body); + } + + /** + * Internal method for capturing the headers from a curl request. + * + * @param handle $ch handle of curl + * @param string $header header + * + * @return void + */ + private function _curlReadHeaders ($ch, $header) + { + $this->storeResponseHeader($header); + return strlen($header); + } +} diff --git a/phpCas/source/CAS/Request/Exception.php b/phpCas/source/CAS/Request/Exception.php new file mode 100644 index 0000000..14ff3c6 --- /dev/null +++ b/phpCas/source/CAS/Request/Exception.php @@ -0,0 +1,45 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * An Exception for problems performing requests + * + * @class CAS_Request_Exception + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_Request_Exception +extends Exception +implements CAS_Exception +{ + +} diff --git a/phpCas/source/CAS/Request/MultiRequestInterface.php b/phpCas/source/CAS/Request/MultiRequestInterface.php new file mode 100644 index 0000000..abc4486 --- /dev/null +++ b/phpCas/source/CAS/Request/MultiRequestInterface.php @@ -0,0 +1,83 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines a class library for performing multiple web requests + * in batches. Implementations of this interface may perform requests serially + * or in parallel. + * + * @class CAS_Request_MultiRequestInterface + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_Request_MultiRequestInterface +{ + + /********************************************************* + * Add Requests + *********************************************************/ + + /** + * Add a new Request to this batch. + * Note, implementations will likely restrict requests to their own concrete + * class hierarchy. + * + * @param CAS_Request_RequestInterface $request request interface + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been + * sent. + * @throws CAS_InvalidArgumentException If passed a Request of the wrong + * implmentation. + */ + public function addRequest (CAS_Request_RequestInterface $request); + + /** + * Retrieve the number of requests added to this batch. + * + * @return number of request elements + */ + public function getNumRequests (); + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. After sending, all requests will have their + * responses poulated. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send (); +} diff --git a/phpCas/source/CAS/Request/RequestInterface.php b/phpCas/source/CAS/Request/RequestInterface.php new file mode 100644 index 0000000..cc11ba4 --- /dev/null +++ b/phpCas/source/CAS/Request/RequestInterface.php @@ -0,0 +1,179 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * This interface defines a class library for performing web requests. + * + * @class CAS_Request_RequestInterface + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +interface CAS_Request_RequestInterface +{ + + /********************************************************* + * Configure the Request + *********************************************************/ + + /** + * Set the URL of the Request + * + * @param string $url url to set + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setUrl ($url); + + /** + * Add a cookie to the request. + * + * @param string $name name of cookie + * @param string $value value of cookie + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookie ($name, $value); + + /** + * Add an array of cookies to the request. + * The cookie array is of the form + * array('cookie_name' => 'cookie_value', 'cookie_name2' => cookie_value2') + * + * @param array $cookies cookies to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addCookies (array $cookies); + + /** + * Add a header string to the request. + * + * @param string $header header to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeader ($header); + + /** + * Add an array of header strings to the request. + * + * @param array $headers headers to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function addHeaders (array $headers); + + /** + * Make the request a POST request rather than the default GET request. + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function makePost (); + + /** + * Add a POST body to the request + * + * @param string $body body to add + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setPostBody ($body); + + + /** + * Specify the path to an SSL CA certificate to validate the server with. + * + * @param string $caCertPath path to cert file + * @param boolean $validate_cn validate CN of SSL certificate + * + * @return void + * @throws CAS_OutOfSequenceException If called after the Request has been sent. + */ + public function setSslCaCert ($caCertPath, $validate_cn = true); + + + + /********************************************************* + * 2. Send the Request + *********************************************************/ + + /** + * Perform the request. + * + * @return bool TRUE on success, FALSE on failure. + * @throws CAS_OutOfSequenceException If called multiple times. + */ + public function send (); + + /********************************************************* + * 3. Access the response + *********************************************************/ + + /** + * Answer the headers of the response. + * + * @return array An array of header strings. + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseHeaders (); + + /** + * Answer HTTP status code of the response + * + * @return int + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseStatusCode (); + + /** + * Answer the body of response. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getResponseBody (); + + /** + * Answer a message describing any errors if the request failed. + * + * @return string + * @throws CAS_OutOfSequenceException If called before the Request has been sent. + */ + public function getErrorMessage (); +} diff --git a/phpCas/source/CAS/TypeMismatchException.php b/phpCas/source/CAS/TypeMismatchException.php new file mode 100644 index 0000000..4a13c2d --- /dev/null +++ b/phpCas/source/CAS/TypeMismatchException.php @@ -0,0 +1,70 @@ + + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ + +/** + * Exception that denotes invalid arguments were passed. + * + * @class CAS_InvalidArgumentException + * @category Authentication + * @package PhpCAS + * @author Adam Franco + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 + * @link https://wiki.jasig.org/display/CASC/phpCAS + */ +class CAS_TypeMismatchException +extends CAS_InvalidArgumentException +{ + /** + * Constructor, provides a nice message. + * + * @param mixed $argument Argument + * @param string $argumentName Argument Name + * @param string $type Type + * @param string $message Error Message + * @param integer $code Code + * + * @return void + */ + public function __construct ( + $argument, $argumentName, $type, $message = '', $code = 0 + ) { + if (is_object($argument)) { + $foundType = get_class($argument).' object'; + } else { + $foundType = gettype($argument); + } + + parent::__construct( + 'type mismatched for parameter ' + . $argumentName . ' (should be \'' . $type .' \'), ' + . $foundType . ' given. ' . $message, $code + ); + } +} +?> diff --git a/update.sql b/update.sql new file mode 100644 index 0000000..3b05790 --- /dev/null +++ b/update.sql @@ -0,0 +1,18 @@ + +ALTER TABLE `resources` +ADD COLUMN `image` VARCHAR(255) NULL DEFAULT NULL COMMENT '' AFTER `name`; + +ALTER TABLE `form_customization` +ADD COLUMN `field_desc` VARCHAR(1000) NULL DEFAULT NULL COMMENT '' AFTER `field_type`; + +CREATE TABLE `settings` ( + `property` VARCHAR(100) NOT NULL COMMENT '', + `value` VARCHAR(1000) NULL COMMENT '', + INDEX `property_index` (`property` ASC) COMMENT ''); + +INSERT INTO settings values ('site_title', ''); +INSERT INTO settings values ('site_logo', ''); +INSERT INTO settings values ('debug_mode', '0'); +INSERT INTO settings values ('advance_start', '0'); +INSERT INTO settings VALUES ('allow_booking_overlap', '1'); +INSERT INTO settings VALUES ('global_message', '');