yannstatic/static/2021/03/17/PHP_Authentification_2FA(RobThree).html

2041 lines
217 KiB
HTML
Raw Normal View History

2024-10-31 20:18:37 +01:00
<!DOCTYPE html><html lang="fr">
<head><meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"><title>PHP Authentification RobThree/TwoFactorAuth - YannStatic</title>
<meta name="description" content="Bibliothèque PHP 2FA">
<link rel="canonical" href="https://static.rnmkcy.eu/2021/03/17/PHP_Authentification_2FA(RobThree).html"><link rel="alternate" type="application/rss+xml" title="YannStatic" href="/feed.xml">
<!-- - include head/favicon.html - -->
<link rel="shortcut icon" type="image/png" href="/assets/favicon/favicon.png"><link rel="stylesheet" href="/assets/css/main.css"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" ><!-- start custom head snippets --><link rel="stylesheet" href="/assets/css/expand.css">
<!-- end custom head snippets --><script>(function() {
window.isArray = function(val) {
return Object.prototype.toString.call(val) === '[object Array]';
};
window.isString = function(val) {
return typeof val === 'string';
};
window.hasEvent = function(event) {
return 'on'.concat(event) in window.document;
};
window.isOverallScroller = function(node) {
return node === document.documentElement || node === document.body || node === window;
};
window.isFormElement = function(node) {
var tagName = node.tagName;
return tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
};
window.pageLoad = (function () {
var loaded = false, cbs = [];
window.addEventListener('load', function () {
var i;
loaded = true;
if (cbs.length > 0) {
for (i = 0; i < cbs.length; i++) {
cbs[i]();
}
}
});
return {
then: function(cb) {
cb && (loaded ? cb() : (cbs.push(cb)));
}
};
})();
})();
(function() {
window.throttle = function(func, wait) {
var args, result, thisArg, timeoutId, lastCalled = 0;
function trailingCall() {
lastCalled = new Date;
timeoutId = null;
result = func.apply(thisArg, args);
}
return function() {
var now = new Date,
remaining = wait - (now - lastCalled);
args = arguments;
thisArg = this;
if (remaining <= 0) {
clearTimeout(timeoutId);
timeoutId = null;
lastCalled = now;
result = func.apply(thisArg, args);
} else if (!timeoutId) {
timeoutId = setTimeout(trailingCall, remaining);
}
return result;
};
};
})();
(function() {
var Set = (function() {
var add = function(item) {
var i, data = this._data;
for (i = 0; i < data.length; i++) {
if (data[i] === item) {
return;
}
}
this.size ++;
data.push(item);
return data;
};
var Set = function(data) {
this.size = 0;
this._data = [];
var i;
if (data.length > 0) {
for (i = 0; i < data.length; i++) {
add.call(this, data[i]);
}
}
};
Set.prototype.add = add;
Set.prototype.get = function(index) { return this._data[index]; };
Set.prototype.has = function(item) {
var i, data = this._data;
for (i = 0; i < data.length; i++) {
if (this.get(i) === item) {
return true;
}
}
return false;
};
Set.prototype.is = function(map) {
if (map._data.length !== this._data.length) { return false; }
var i, j, flag, tData = this._data, mData = map._data;
for (i = 0; i < tData.length; i++) {
for (flag = false, j = 0; j < mData.length; j++) {
if (tData[i] === mData[j]) {
flag = true;
break;
}
}
if (!flag) { return false; }
}
return true;
};
Set.prototype.values = function() {
return this._data;
};
return Set;
})();
window.Lazyload = (function(doc) {
var queue = {js: [], css: []}, sources = {js: {}, css: {}}, context = this;
var createNode = function(name, attrs) {
var node = doc.createElement(name), attr;
for (attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
node.setAttribute(attr, attrs[attr]);
}
}
return node;
};
var end = function(type, url) {
var s, q, qi, cbs, i, j, cur, val, flag;
if (type === 'js' || type ==='css') {
s = sources[type], q = queue[type];
s[url] = true;
for (i = 0; i < q.length; i++) {
cur = q[i];
if (cur.urls.has(url)) {
qi = cur, val = qi.urls.values();
qi && (cbs = qi.callbacks);
for (flag = true, j = 0; j < val.length; j++) {
cur = val[j];
if (!s[cur]) {
flag = false;
}
}
if (flag && cbs && cbs.length > 0) {
for (j = 0; j < cbs.length; j++) {
cbs[j].call(context);
}
qi.load = true;
}
}
}
}
};
var load = function(type, urls, callback) {
var s, q, qi, node, i, cur,
_urls = typeof urls === 'string' ? new Set([urls]) : new Set(urls), val, url;
if (type === 'js' || type ==='css') {
s = sources[type], q = queue[type];
for (i = 0; i < q.length; i++) {
cur = q[i];
if (_urls.is(cur.urls)) {
qi = cur;
break;
}
}
val = _urls.values();
if (qi) {
callback && (qi.load || qi.callbacks.push(callback));
callback && (qi.load && callback());
} else {
q.push({
urls: _urls,
callbacks: callback ? [callback] : [],
load: false
});
for (i = 0; i < val.length; i++) {
node = null, url = val[i];
if (s[url] === undefined) {
(type === 'js' ) && (node = createNode('script', { src: url }));
(type === 'css') && (node = createNode('link', { rel: 'stylesheet', href: url }));
if (node) {
node.onload = (function(type, url) {
return function() {
end(type, url);
};
})(type, url);
(doc.head || doc.body).appendChild(node);
s[url] = false;
}
}
}
}
}
};
return {
js: function(url, callback) {
load('js', url, callback);
},
css: function(url, callback) {
load('css', url, callback);
}
};
})(this.document);
})();
</script><script>
(function() {
var TEXT_VARIABLES = {
version: '2.2.6',
sources: {
font_awesome: 'https://use.fontawesome.com/releases/v5.0.13/css/all.css',
jquery: '/assets/js/jquery.min.js',
leancloud_js_sdk: '//cdn.jsdelivr.net/npm/leancloud-storage@3.13.2/dist/av-min.js',
chart: 'https://cdn.bootcss.com/Chart.js/2.7.2/Chart.bundle.min.js',
gitalk: {
js: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.js',
css: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.css'
},
valine: 'https://unpkg.com/valine/dist/Valine.min.js'
},
site: {
toc: {
selectors: 'h1,h2,h3'
}
},
paths: {
search_js: '/assets/search.js'
}
};
window.TEXT_VARIABLES = TEXT_VARIABLES;
})();
</script>
</head>
<body>
<div class="root" data-is-touch="false">
<div class="layout--page js-page-root"><!----><div class="page__main js-page-main page__viewport hide-footer has-aside has-aside cell cell--auto">
<div class="page__main-inner"><div class="page__header d-print-none"><header class="header"><div class="main">
<div class="header__title">
<div class="header__brand"><svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="478.9473684210526" viewBox="0, 0, 400,478.9473684210526"><g id="svgg"><path id="path0" d="M308.400 56.805 C 306.970 56.966,303.280 57.385,300.200 57.738 C 290.906 58.803,278.299 59.676,269.200 59.887 L 260.600 60.085 259.400 61.171 C 258.010 62.428,256.198 63.600,255.645 63.600 C 255.070 63.600,252.887 65.897,252.598 66.806 C 252.460 67.243,252.206 67.600,252.034 67.600 C 251.397 67.600,247.206 71.509,247.202 72.107 C 247.201 72.275,246.390 73.190,245.400 74.138 C 243.961 75.517,243.598 76.137,243.592 77.231 C 243.579 79.293,241.785 83.966,240.470 85.364 C 239.176 86.740,238.522 88.365,237.991 91.521 C 237.631 93.665,236.114 97.200,235.554 97.200 C 234.938 97.200,232.737 102.354,232.450 104.472 C 232.158 106.625,230.879 109.226,229.535 110.400 C 228.933 110.926,228.171 113.162,226.434 119.500 C 226.178 120.435,225.795 121.200,225.584 121.200 C 225.373 121.200,225.200 121.476,225.200 121.813 C 225.200 122.149,224.885 122.541,224.500 122.683 C 223.606 123.013,223.214 123.593,223.204 124.600 C 223.183 126.555,220.763 132.911,219.410 134.562 C 218.443 135.742,217.876 136.956,217.599 138.440 C 217.041 141.424,215.177 146.434,214.532 146.681 C 214.240 146.794,214.000 147.055,214.000 147.261 C 214.000 147.467,213.550 148.086,213.000 148.636 C 212.450 149.186,212.000 149.893,212.000 150.208 C 212.000 151.386,208.441 154.450,207.597 153.998 C 206.319 153.315,204.913 150.379,204.633 147.811 C 204.365 145.357,202.848 142.147,201.759 141.729 C 200.967 141.425,199.200 137.451,199.200 135.974 C 199.200 134.629,198.435 133.224,196.660 131.311 C 195.363 129.913,194.572 128.123,193.870 125.000 C 193.623 123.900,193.236 122.793,193.010 122.540 C 190.863 120.133,190.147 118.880,188.978 115.481 C 188.100 112.928,187.151 111.003,186.254 109.955 C 185.358 108.908,184.518 107.204,183.847 105.073 C 183.280 103.273,182.497 101.329,182.108 100.753 C 181.719 100.177,180.904 98.997,180.298 98.131 C 179.693 97.265,178.939 95.576,178.624 94.378 C 178.041 92.159,177.125 90.326,175.023 87.168 C 174.375 86.196,173.619 84.539,173.342 83.486 C 172.800 81.429,171.529 79.567,170.131 78.785 C 169.654 78.517,168.697 77.511,168.006 76.549 C 167.316 75.587,166.594 74.800,166.402 74.800 C 166.210 74.800,164.869 73.633,163.421 72.206 C 160.103 68.936,161.107 69.109,146.550 69.301 C 133.437 69.474,128.581 70.162,126.618 72.124 C 126.248 72.495,125.462 72.904,124.872 73.033 C 124.282 73.163,123.088 73.536,122.219 73.863 C 121.349 74.191,119.028 74.638,117.061 74.858 C 113.514 75.254,109.970 76.350,108.782 77.419 C 107.652 78.436,100.146 80.400,97.388 80.400 C 95.775 80.400,93.167 81.360,91.200 82.679 C 90.430 83.195,89.113 83.804,88.274 84.031 C 85.875 84.681,78.799 90.910,74.400 96.243 L 73.400 97.456 73.455 106.028 C 73.526 117.055,74.527 121.238,77.820 124.263 C 78.919 125.273,80.400 127.902,80.400 128.842 C 80.400 129.202,81.075 130.256,81.900 131.186 C 83.563 133.059,85.497 136.346,86.039 138.216 C 86.233 138.886,87.203 140.207,88.196 141.153 C 89.188 142.098,90.000 143.104,90.000 143.388 C 90.000 144.337,92.129 148.594,92.869 149.123 C 93.271 149.410,93.600 149.831,93.600 150.059 C 93.600 150.286,93.932 150.771,94.337 151.136 C 94.743 151.501,95.598 153.004,96.237 154.475 C 96.877 155.947,97.760 157.351,98.200 157.596 C 98.640 157.841,99.900 159.943,101.000 162.267 C 102.207 164.817,103.327 166.644,103.825 166.876 C 104.278 167.087,105.065 168.101,105.573 169.130 C 107.658 173.348,108.097 174.093,110.006 176.647 C 111.103 178.114,112.000 179.725,112.000 180.227 C 112.000 181.048,113.425 183.163,114.678 184.200 C 115.295 184.711,117.396 188.733,117.720 190.022 C 117.855 190.562,118.603 191.633,119.381 192.402 C 120.160 193.171,121.496 195.258,122.351 197.039 C 123.206 198.820,124.167 200.378,124.487 200.501 C 124.807 200.624,125.953 202.496,127.034 204.662 C 128.114 206.828,129.676 209.299,130.505 210.153 C 131.333 211.007,132.124 212.177,132.262 212.753 C 132.618 214.239,134.291 217.048,136.288 219.5
" href="/">YannStatic</a></div><!--<button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button>--><!-- <li><button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button></li> -->
<!-- Champ de recherche -->
<div id="searchbox" class="search search--dark" style="visibility: visible">
<div class="main">
<div class="search__header"></div>
<div class="search-bar">
<div class="search-box js-search-box">
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
<input id="search-input" type="text" />
<!-- <div class="search-box__icon-clear js-icon-clear">
<a><i class="fas fa-times"></i></a>
</div> -->
</div>
</div>
</div>
</div>
<!-- Script pointing to search-script.js -->
<script>/*!
* Simple-Jekyll-Search
* Copyright 2015-2020, Christian Fei
* Licensed under the MIT License.
*/
(function(){
'use strict'
var _$Templater_7 = {
compile: compile,
setOptions: setOptions
}
const options = {}
options.pattern = /\{(.*?)\}/g
options.template = ''
options.middleware = function () {}
function setOptions (_options) {
options.pattern = _options.pattern || options.pattern
options.template = _options.template || options.template
if (typeof _options.middleware === 'function') {
options.middleware = _options.middleware
}
}
function compile (data) {
return options.template.replace(options.pattern, function (match, prop) {
const value = options.middleware(prop, data[prop], options.template)
if (typeof value !== 'undefined') {
return value
}
return data[prop] || match
})
}
'use strict';
function fuzzysearch (needle, haystack) {
var tlen = haystack.length;
var qlen = needle.length;
if (qlen > tlen) {
return false;
}
if (qlen === tlen) {
return needle === haystack;
}
outer: for (var i = 0, j = 0; i < qlen; i++) {
var nch = needle.charCodeAt(i);
while (j < tlen) {
if (haystack.charCodeAt(j++) === nch) {
continue outer;
}
}
return false;
}
return true;
}
var _$fuzzysearch_1 = fuzzysearch;
'use strict'
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
function FuzzySearchStrategy () {
this.matches = function (string, crit) {
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
}
}
'use strict'
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
function LiteralSearchStrategy () {
this.matches = function (str, crit) {
if (!str) return false
str = str.trim().toLowerCase()
crit = crit.trim().toLowerCase()
return crit.split(' ').filter(function (word) {
return str.indexOf(word) >= 0
}).length === crit.split(' ').length
}
}
'use strict'
var _$Repository_4 = {
put: put,
clear: clear,
search: search,
setOptions: __setOptions_4
}
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
function NoSort () {
return 0
}
const data = []
let opt = {}
opt.fuzzy = false
opt.limit = 10
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
opt.sort = NoSort
opt.exclude = []
function put (data) {
if (isObject(data)) {
return addObject(data)
}
if (isArray(data)) {
return addArray(data)
}
return undefined
}
function clear () {
data.length = 0
return data
}
function isObject (obj) {
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
}
function isArray (obj) {
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
}
function addObject (_data) {
data.push(_data)
return data
}
function addArray (_data) {
const added = []
clear()
for (let i = 0, len = _data.length; i < len; i++) {
if (isObject(_data[i])) {
added.push(addObject(_data[i]))
}
}
return added
}
function search (crit) {
if (!crit) {
return []
}
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
}
function __setOptions_4 (_opt) {
opt = _opt || {}
opt.fuzzy = _opt.fuzzy || false
opt.limit = _opt.limit || 10
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
opt.sort = _opt.sort || NoSort
opt.exclude = _opt.exclude || []
}
function findMatches (data, crit, strategy, opt) {
const matches = []
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
const match = findMatchesInObject(data[i], crit, strategy, opt)
if (match) {
matches.push(match)
}
}
return matches
}
function findMatchesInObject (obj, crit, strategy, opt) {
for (const key in obj) {
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
return obj
}
}
}
function isExcluded (term, excludedTerms) {
for (let i = 0, len = excludedTerms.length; i < len; i++) {
const excludedTerm = excludedTerms[i]
if (new RegExp(excludedTerm).test(term)) {
return true
}
}
return false
}
/* globals ActiveXObject:false */
'use strict'
var _$JSONLoader_2 = {
load: load
}
function load (location, callback) {
const xhr = getXHR()
xhr.open('GET', location, true)
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
xhr.send()
}
function createStateChangeListener (xhr, callback) {
return function () {
if (xhr.readyState === 4 && xhr.status === 200) {
try {
callback(null, JSON.parse(xhr.responseText))
} catch (err) {
callback(err, null)
}
}
}
}
function getXHR () {
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
}
'use strict'
var _$OptionsValidator_3 = function OptionsValidator (params) {
if (!validateParams(params)) {
throw new Error('-- OptionsValidator: required options missing')
}
if (!(this instanceof OptionsValidator)) {
return new OptionsValidator(params)
}
const requiredOptions = params.required
this.getRequiredOptions = function () {
return requiredOptions
}
this.validate = function (parameters) {
const errors = []
requiredOptions.forEach(function (requiredOptionName) {
if (typeof parameters[requiredOptionName] === 'undefined') {
errors.push(requiredOptionName)
}
})
return errors
}
function validateParams (params) {
if (!params) {
return false
}
return typeof params.required !== 'undefined' && params.required instanceof Array
}
}
'use strict'
var _$utils_9 = {
merge: merge,
isJSON: isJSON
}
function merge (defaultParams, mergeParams) {
const mergedOptions = {}
for (const option in defaultParams) {
mergedOptions[option] = defaultParams[option]
if (typeof mergeParams[option] !== 'undefined') {
mergedOptions[option] = mergeParams[option]
}
}
return mergedOptions
}
function isJSON (json) {
try {
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
return true
}
return false
} catch (err) {
return false
}
}
var _$src_8 = {};
(function (window) {
'use strict'
let options = {
searchInput: null,
resultsContainer: null,
json: [],
success: Function.prototype,
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
templateMiddleware: Function.prototype,
sortMiddleware: function () {
return 0
},
noResultsText: 'No results found',
limit: 10,
fuzzy: false,
debounceTime: null,
exclude: []
}
let debounceTimerHandle
const debounce = function (func, delayMillis) {
if (delayMillis) {
clearTimeout(debounceTimerHandle)
debounceTimerHandle = setTimeout(func, delayMillis)
} else {
func.call()
}
}
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
/* removed: const _$Templater_7 = require('./Templater') */;
/* removed: const _$Repository_4 = require('./Repository') */;
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
const optionsValidator = _$OptionsValidator_3({
required: requiredOptions
})
/* removed: const _$utils_9 = require('./utils') */;
window.SimpleJekyllSearch = function (_options) {
const errors = optionsValidator.validate(_options)
if (errors.length > 0) {
throwError('You must specify the following required options: ' + requiredOptions)
}
options = _$utils_9.merge(options, _options)
_$Templater_7.setOptions({
template: options.searchResultTemplate,
middleware: options.templateMiddleware
})
_$Repository_4.setOptions({
fuzzy: options.fuzzy,
limit: options.limit,
sort: options.sortMiddleware,
exclude: options.exclude
})
if (_$utils_9.isJSON(options.json)) {
initWithJSON(options.json)
} else {
initWithURL(options.json)
}
const rv = {
search: search
}
typeof options.success === 'function' && options.success.call(rv)
return rv
}
function initWithJSON (json) {
_$Repository_4.put(json)
registerInput()
}
function initWithURL (url) {
_$JSONLoader_2.load(url, function (err, json) {
if (err) {
throwError('failed to get JSON (' + url + ')')
}
initWithJSON(json)
})
}
function emptyResultsContainer () {
options.resultsContainer.innerHTML = ''
}
function appendToResultsContainer (text) {
options.resultsContainer.innerHTML += text
}
function registerInput () {
options.searchInput.addEventListener('input', function (e) {
if (isWhitelistedKey(e.which)) {
emptyResultsContainer()
debounce(function () { search(e.target.value) }, options.debounceTime)
}
})
}
function search (query) {
if (isValidQuery(query)) {
emptyResultsContainer()
render(_$Repository_4.search(query), query)
}
}
function render (results, query) {
const len = results.length
if (len === 0) {
return appendToResultsContainer(options.noResultsText)
}
for (let i = 0; i < len; i++) {
results[i].query = query
appendToResultsContainer(_$Templater_7.compile(results[i]))
}
}
function isValidQuery (query) {
return query && query.length > 0
}
function isWhitelistedKey (key) {
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
}
function throwError (message) {
throw new Error('SimpleJekyllSearch --- ' + message)
}
})(window)
}());
</script>
<!-- Configuration -->
<script>
SimpleJekyllSearch({
searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('results-container'),
json: '/search.json',
//searchResultTemplate: '<li><a href="https://static.rnmkcy.eu{url}">{date}&nbsp;{title}</a></li>'
searchResultTemplate: '<li><a href="{url}">{date}&nbsp;{title}</a></li>'
})
</script>
<!-- Fin déclaration champ de recherche --></div><nav class="navigation">
<ul><li class="navigation__item"><a href="/archive.html">Etiquettes</a></li><li class="navigation__item"><a href="/htmldoc.html">Documents</a></li><li class="navigation__item"><a href="/liens_ttrss.html">Liens</a></li><li class="navigation__item"><a href="/aide-jekyll-text-theme.html">Aide</a></li></ul>
</nav></div>
</header>
</div><div class="page__content"><div class ="main"><div class="grid grid--reverse">
<div class="col-main cell cell--auto"><!-- start custom main top snippet --><div id="results-container" class="search-result js-search-result"></div><!-- end custom main top snippet -->
<article itemscope itemtype="http://schema.org/Article"><div class="article__header"><header><h1 style="color:Tomato;">PHP Authentification RobThree/TwoFactorAuth</h1></header></div><meta itemprop="headline" content="PHP Authentification RobThree/TwoFactorAuth"><div class="article__info clearfix"><ul class="left-col menu"><li>
2024-11-08 14:10:33 +01:00
<a class="button button--secondary button--pill button--sm" style="color:#00FFFF" href="/archive.html?tag=virtuel">virtuel</a>
2024-10-31 20:18:37 +01:00
</li><li>
2024-11-08 14:10:33 +01:00
<a class="button button--secondary button--pill button--sm" style="color:#00FFFF" href="/archive.html?tag=authentification">authentification</a>
2024-10-31 20:18:37 +01:00
</li></ul><ul class="right-col menu"><li>
<i class="far fa-calendar-alt"></i>&nbsp;<span title="Création" style="color:#FF00FF">17&nbsp;mars&nbsp;&nbsp;2021</span>
<span title="Modification" style="color:#00FF7F">30&nbsp;mars&nbsp;&nbsp;2021</span></li></ul></div><meta itemprop="datePublished" content="2021-03-30T00:00:00+02:00">
<meta itemprop="keywords" content="virtuel,authentification"><div class="js-article-content">
<div class="layout--article"><!-- start custom article top snippet -->
<style>
#myBtn {
display: none;
position: fixed;
bottom: 10px;
right: 10px;
z-index: 99;
font-size: 12px;
font-weight: bold;
border: none;
outline: none;
background-color: white;
color: black;
cursor: pointer;
padding: 5px;
border-radius: 4px;
}
#myBtn:hover {
background-color: #555;
}
</style>
<button onclick="topFunction()" id="myBtn" title="Haut de page">&#8679;</button>
<script>
//Get the button
var mybutton = document.getElementById("myBtn");
// When the user scrolls down 20px from the top of the document, show the button
window.onscroll = function() {scrollFunction()};
function scrollFunction() {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
mybutton.style.display = "block";
} else {
mybutton.style.display = "none";
}
}
// When the user clicks on the button, scroll to the top of the document
function topFunction() {
document.body.scrollTop = 0;
document.documentElement.scrollTop = 0;
}
</script>
<!-- end custom article top snippet -->
<div class="article__content" itemprop="articleBody"><details>
<summary><b>Afficher/cacher Sommaire</b></summary>
<!-- affichage sommaire -->
<div class="toc-aside js-toc-root"></div>
</details><h2 id="bibliothèque-php-2fa">Bibliothèque PHP 2FA</h2>
<p><em>Bibliothèque PHP pour lauthentification à deux facteurs</em></p>
<h3 id="prérequis">Prérequis</h3>
<ul>
<li>Unzip : <code class="language-plaintext highlighter-rouge">sudo apt install unzip</code></li>
<li>Testé sur PHP 5.6 jusquà 8.0.</li>
<li><a href="http://php.net/manual/en/book.curl.php">cURL</a> en utilisant le <code class="language-plaintext highlighter-rouge">QRServerProvider</code> (par défaut), <code class="language-plaintext highlighter-rouge">ImageChartsQRCodeProvider</code> ou <code class="language-plaintext highlighter-rouge">QRicketProvider</code> mais vous pouvez aussi fournir votre propre fournisseur de QR-code.</li>
<li><a href="http://php.net/manual/en/function.random-bytes.php">random_bytes()</a>, <a href="http://php.net/manual/en/book.mcrypt.php">MCrypt</a>, <a href="http://php.net/manual/en/book.openssl.php">OpenSSL</a> ou <a href="http://php.net/manual/en/book.hash.php">Hash</a> selon le RNG intégré que vous utilisez (TwoFactorAuth essaiera dautodétecter et dutiliser le meilleur disponible) ; cependant : nhésitez pas à fournir votre propre (CS)RNG.</li>
</ul>
<p>En option, vous pouvez avoir besoin de :</p>
<ul>
<li><a href="https://github.com/endroid/qr-code">endroid/qr-code</a> si vous utilisez <code class="language-plaintext highlighter-rouge">EndroidQrCodeProvider</code> ou <code class="language-plaintext highlighter-rouge">EndroidQrCodeWithLogoProvider</code>.</li>
<li><a href="https://github.com/Bacon/BaconQrCode">bacon/bacon-qr-code</a> si vous utilisez <code class="language-plaintext highlighter-rouge">BaconQrCodeProvider</code>.</li>
</ul>
<p><a href="/2019/12/25/php-composer.html">Installer PHP composer</a></p>
<p>Bash dinstallation</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano composer.sh
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh
EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
ACTUAL_SIGNATURE="$(php -r "echo hash_file('SHA384', 'composer-setup.php');")"
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]
then
&gt;&amp;2 echo 'ERROR: Invalid installer signature'
rm composer-setup.php
exit 1
fi
php composer-setup.php --quiet
RESULT=$?
rm composer-setup.php
exit $RESULT
</code></pre></div></div>
<p><a href="https://github.com/RobThree/TwoFactorAuth">PHP library for Two Factor Authentication</a><br />
Installation, exécutez la commande suivante :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>php composer.phar require robthree/twofactorauth
</code></pre></div></div>
<p><img src="/images/php2fa01.png" alt="" /></p>
<h3 id="usage">Usage</h3>
<p>Voici quelques extraits de code qui devraient vous aider à démarrer…</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Créez une instance TwoFactorAuth</span>
<span class="nv">$tfa</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RobThree\Auth\TwoFactorAuth</span><span class="p">(</span><span class="s1">'My Company'</span><span class="p">)</span> <span class="p">;</span>
</code></pre></div></div>
<p>Le constructeur de la classe TwoFactorAuth accepte 7 arguments (tous facultatifs) :</p>
<table>
<thead>
<tr>
<th>Argument</th>
<th>Valeur par défaut</th>
<th>Utilisation</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="language-plaintext highlighter-rouge">$issuer</code></td>
<td><code class="language-plaintext highlighter-rouge">null</code></td>
<td>Sera affiché dans lapplication comme nom de lémetteur.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">$digits</code></td>
<td><code class="language-plaintext highlighter-rouge">6</code></td>
<td>Le nombre de chiffres des codes résultants.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">$period</code></td>
<td><code class="language-plaintext highlighter-rouge">30</code></td>
<td>Le nombre de secondes pendant lesquelles le code sera valide.</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">$algorithm</code></td>
<td><code class="language-plaintext highlighter-rouge">sha1</code></td>
<td>Lalgorithme utilisé (un parmi <code class="language-plaintext highlighter-rouge">sha1</code>, <code class="language-plaintext highlighter-rouge">sha256</code>, <code class="language-plaintext highlighter-rouge">sha512</code>, <code class="language-plaintext highlighter-rouge">md5</code>)</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">$qrcodeprovider</code></td>
<td><code class="language-plaintext highlighter-rouge">null</code></td>
<td>Fournisseur de code QR (plus dinformations à ce sujet plus tard)</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">$rngprovider</code></td>
<td><code class="language-plaintext highlighter-rouge">null</code></td>
<td>Fournisseur de générateur de nombres aléatoires (plus dinformations à ce sujet plus tard)</td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">$timeprovider</code></td>
<td><code class="language-plaintext highlighter-rouge">null</code></td>
<td>Fournisseur de lheure (plus sur ce sujet plus tard)</td>
</tr>
</tbody>
</table>
<p>Ces arguments sont tous <code class="language-plaintext highlighter-rouge">write once</code> ; la classe utilisera ces valeurs, pour toute sa durée de vie, lors de la génération / du calcul des codes. Le nombre de chiffres, la période et lalgorithme sont tous définis en fonction des valeurs utilisées (et supportées) par lapplication Authenticator de Google. Vous pouvez spécifier 8 chiffres, une période de 45 secondes et lalgorithme sha256, mais lapplication dauthentification (quil sagisse de lapplication de Google, dAuthy ou dune autre application) peut ou non prendre en charge ces valeurs. Votre expérience peut varier ; soyez prudent si vous ne contrôlez pas lapplication utilisée par votre public.</p>
<h4 id="étape-1--créer-une-clé-partagée-secrète">Étape 1 : Créer une clé partagée secrète</h4>
<p>Lorsquun utilisateur souhaite configurer lauthentification à deux facteurs (ou, plus correctement, lauthentification à plusieurs facteurs), vous devez créer un secret. Ce sera votre <strong>secret partagé</strong>. Ce secret devra être saisi par lutilisateur dans son application. Cela peut être fait manuellement, auquel cas vous affichez simplement le secret et demandez à lutilisateur de le saisir dans lapplication :</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$secret</span> <span class="o">=</span> <span class="nv">$tfa</span><span class="o">-&gt;</span><span class="nf">createSecret</span><span class="p">()</span> <span class="p">;</span>
</code></pre></div></div>
<p>La méthode <code class="language-plaintext highlighter-rouge">createSecret()</code> accepte deux arguments : <code class="language-plaintext highlighter-rouge">$bits</code> (par défaut : <code class="language-plaintext highlighter-rouge">80</code>) et <code class="language-plaintext highlighter-rouge">$requirecryptosecure</code> (par défaut : <code class="language-plaintext highlighter-rouge">true</code>). Le premier est le nombre de bits générés pour le secret partagé. Assurez-vous que cet argument est un multiple de 8 et, encore une fois, gardez à lesprit que toutes les combinaisons ne sont pas forcément supportées par toutes les applications. Lauthentificateur de Google semble satisfait avec 80 et 160, la valeur par défaut est 80 car cest ce que la plupart des sites (que je connais) utilisent actuellement ; cependant une valeur de 160 ou plus est recommandée (voir <a href="https://tools.ietf.org/html/rfc4226#section-4">RFC 4226 - Algorithm Requirements</a>). Ce dernier est utilisé pour sassurer que le secret est cryptographiquement sécurisé ; si vous ne vous souciez pas beaucoup des secrets cryptographiquement sécurisés, vous pouvez spécifier <code class="language-plaintext highlighter-rouge">false</code> et utiliser un fournisseur de RNG <strong>non</strong>-cryptographiquement sécurisé.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Afficher le secret partagé
<span class="nt">&lt;p&gt;</span>Veuillez entrer le code suivant dans votre application : '<span class="cp">&lt;?php</span> <span class="k">echo</span> <span class="nv">$secret</span> <span class="p">;</span> <span class="cp">?&gt;</span>'<span class="nt">&lt;/p&gt;</span>
</code></pre></div></div>
<p>Une autre façon, plus conviviale, de faire entrer le secret partagé dans lapplication consiste à générer un <a href="http://en.wikipedia.org/wiki/QR_code">QR-code</a> qui peut être scanné par lapplication.<br />
Pour générer ces codes QR, vous pouvez utiliser lune des classes intégrées <code class="language-plaintext highlighter-rouge">QRProvider</code> :</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">QRServerProvider</code> (par défaut)</li>
<li><code class="language-plaintext highlighter-rouge">ImageChartsQRCodeProvider</code>.</li>
<li><code class="language-plaintext highlighter-rouge">QRicketProvider</code> (par défaut)</li>
<li><code class="language-plaintext highlighter-rouge">EndroidQrCodeProvider</code> (nécessite linstallation de <code class="language-plaintext highlighter-rouge">endroid/qr-code</code>)</li>
<li><code class="language-plaintext highlighter-rouge">EndroidQrCodeWithLogoProvider</code> (identique, mais supportant les images intégrées)</li>
<li><code class="language-plaintext highlighter-rouge">BaconQrCodeProvider</code> (nécessite linstallation de <code class="language-plaintext highlighter-rouge">bacon/bacon-qr-code</code>)</li>
</ol>
<p>…ou implémentez votre propre fournisseur. Pour implémenter votre propre fournisseur, tout ce que vous avez à faire est dimplémenter linterface <code class="language-plaintext highlighter-rouge">IQRCodeProvider</code>.</p>
<p>Vous pouvez utiliser les fournisseurs intégrés mentionnés précédemment pour servir dexemple ou lire le chapitre suivant de ce fichier. Les classes intégrées utilisent toutes une troisième partie (par exemple externe) (Image-charts, QRServer et QRicket) pour le travail difficile de génération de QR-codes (note : chacun de ces services pourrait à un moment donné ne pas être disponible ou imposer des limitations au nombre de codes générés par jour, heure, etc.)<br />
Cependant, vous pouvez facilement utiliser un projet comme <a href="http://phpqrcode.sourceforge.net/">PHP QR Code</a> (ou lun des <a href="https://packagist.org/search/?q=qr">nombreux autres</a>) pour générer vos QR-codes sans dépendre de sources externes. Plus tard, nous allons <a href="#qr-code-providers">démontrer</a> comment faire cela.</p>
<p>Les fournisseurs intégrés ont tous des “ réglages “ spécifiques que vous pouvez “ appliquer “. Certains prennent en charge différentes couleurs, dautres vous permettent de spécifier le format dimage souhaité, etc. Ce quils ont tous en commun, cest quils renvoient un code QR sous la forme dun blob binaire qui, à son tour, sera transformé en <a href="http://en.wikipedia.org/wiki/Data_URI_scheme">URI de données</a> par la classe <code class="language-plaintext highlighter-rouge">TwoFactorAuth</code>. Cela vous permet dafficher facilement limage sans nécessiter dallers-retours supplémentaires entre le navigateur et le serveur et vice-versa.</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Afficher le code QR à l'utilisateur
<span class="nt">&lt;p&gt;</span>Scannez l'image suivante avec votre application :<span class="nt">&lt;/p&gt;</span>
<span class="nt">&lt;p&gt;&lt;img</span> <span class="na">src=</span><span class="s">"</span><span class="cp">&lt;?php</span> <span class="k">echo</span> <span class="nv">$tfa</span><span class="o">-&gt;</span><span class="nf">getQRCodeImageAsDataUri</span><span class="p">(</span><span class="s1">'Bob Ross'</span><span class="p">,</span> <span class="nv">$secret</span><span class="p">)</span> <span class="p">;</span> <span class="cp">?&gt;</span><span class="s">"</span><span class="nt">&gt;&lt;/p&gt;</span>
</code></pre></div></div>
<p>Lors de la sortie dun QR-code, vous pouvez choisir un <code class="language-plaintext highlighter-rouge">$label</code> pour lutilisateur (qui, lors de la saisie manuelle dun secret partagé, devra être choisi par lutilisateur). Ce label peut être une chaîne vide ou <code class="language-plaintext highlighter-rouge">null</code>. On peut aussi spécifier une <code class="language-plaintext highlighter-rouge">$taille</code> (en pixels, largeur == hauteur) pour laquelle nous utilisons une valeur par défaut de <code class="language-plaintext highlighter-rouge">200</code>.</p>
<h4 id="étape-2--vérification-du-secret-partagé">Étape 2 : Vérification du secret partagé</h4>
<p>Lorsque le secret partagé est ajouté à lapplication, lapplication sera prête à commencer à générer des codes qui “expirent” chaque “<code class="language-plaintext highlighter-rouge">$period</code>” nombre de secondes. Pour sassurer que le secret a été saisi ou scanné correctement, vous devez le vérifier en demandant à lutilisateur de saisir un code généré. Pour vérifier si le code généré est valide, appelez la méthode <code class="language-plaintext highlighter-rouge">verifyCode()</code> :</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Vérifier le code</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nv">$tfa</span><span class="o">-&gt;</span><span class="nf">verifyCode</span><span class="p">(</span><span class="nv">$_SESSION</span><span class="p">[</span><span class="s1">'secret'</span><span class="p">],</span> <span class="nv">$_POST</span><span class="p">[</span><span class="s1">'verification'</span><span class="p">])</span> <span class="p">;</span>
</code></pre></div></div>
<p>Si vous effectuez des validations supplémentaires avec vos valeurs <code class="language-plaintext highlighter-rouge">$_POST</code>, assurez-vous que le code est toujours soumis en tant que chaîne de caractères - même si cest un code numérique, le convertir en entier nest pas fiable. De plus, vous pouvez avoir besoin de stocker <code class="language-plaintext highlighter-rouge">$secret</code> dans une <code class="language-plaintext highlighter-rouge">$_SESSION</code> ou un autre stockage persistant entre les requêtes. <code class="language-plaintext highlighter-rouge">verifyCode()</code> retournera soit <code class="language-plaintext highlighter-rouge">true</code> (le code est valide) soit <code class="language-plaintext highlighter-rouge">false</code> (le code nest pas valide ; aucun point pour vous !).</p>
<p>La fonction <code class="language-plaintext highlighter-rouge">verifyCode()</code> accepte, en plus de <code class="language-plaintext highlighter-rouge">$secret</code> et <code class="language-plaintext highlighter-rouge">$code</code>, <u>trois arguments</u> supplémentaires.</p>
<ol>
<li><u>Le premier</u> étant <code class="language-plaintext highlighter-rouge">$discrepancy</code>. Comme les codes TOTP sont basés sur le temps(“tranches”), il est très important que le serveur (mais aussi le client) ait une date/heure correcte. Mais comme les deux peuvent différer un peu, nous accordons généralement une certaine marge de manœuvre. Parce que les codes générés sont valides pour une période spécifique (vous vous souvenez de largument <code class="language-plaintext highlighter-rouge">$period</code> dans le constructeur de <code class="language-plaintext highlighter-rouge">TwoFactorAuth</code> ?) nous vérifions généralement la période directement avant et la période directement après lheure actuelle lors de la validation des codes. Ainsi, lorsque lheure courante est <code class="language-plaintext highlighter-rouge">14:34:21</code>, ce qui donne une tranche de temps courante de <code class="language-plaintext highlighter-rouge">14:34:00</code> à <code class="language-plaintext highlighter-rouge">14:34:30</code>, nous calculons/vérifions également les codes pour <code class="language-plaintext highlighter-rouge">14:33:30</code> à <code class="language-plaintext highlighter-rouge">14:34:00</code> et pour <code class="language-plaintext highlighter-rouge">14:34:30</code> à <code class="language-plaintext highlighter-rouge">14:35:00</code>. Cela nous donne une fenêtre de <code class="language-plaintext highlighter-rouge">14:33:30</code> à <code class="language-plaintext highlighter-rouge">14:35:00</code>. Largument <code class="language-plaintext highlighter-rouge">$discrepancy</code> spécifie le nombre de périodes (ou : tranches de temps) que nous vérifions dans les deux directions de lheure actuelle. La valeur par défaut de <code class="language-plaintext highlighter-rouge">$discrepancy</code> est de <code class="language-plaintext highlighter-rouge">1</code>, ce qui donne (max.) 3 vérifications de périodes : -1, courant et +1 période. Un <code class="language-plaintext highlighter-rouge">$discrepancy</code> de <code class="language-plaintext highlighter-rouge">4</code> donne une fenêtre plus large (ou : une plus grande différence de temps entre le client et le serveur) de -4, -3, -2, -1, current, +1, +2, +3 et +4 périodes.</li>
<li><u>Le second</u>, <code class="language-plaintext highlighter-rouge">$time</code>, vous permet de vérifier un code pour un moment précis dans le temps. Cet argument na pas de réelle utilité pratique mais peut être pratique pour unittesting etc. La valeur par défaut, <code class="language-plaintext highlighter-rouge">null</code>, signifie : utiliser le temps actuel.</li>
<li><u>Le troisième</u>, <code class="language-plaintext highlighter-rouge">$timeslice</code>, est un argument de sortie ; la valeur retournée dans <code class="language-plaintext highlighter-rouge">$timeslice</code> est la valeur du timeslice qui correspond au code (sil existe). Cette valeur sera 0 si le code ne correspond pas et non nulle si le code correspond. Cette valeur peut être stockée avec lutilisateur et peut être utilisée pour empêcher les attaques par rejeu. Tout ce que vous avez à faire est de vous assurer, lors dune connexion réussie, que <code class="language-plaintext highlighter-rouge">$timeslice</code> est plus grand que le timeslice précédemment stocké.</li>
</ol>
<h3 id="étape-3--stocker-secret-avec-lutilisateur-et-cest-fini-">Étape 3 : Stocker <code class="language-plaintext highlighter-rouge">$secret</code> avec lutilisateur et cest fini !</h3>
<p>Ok, donc maintenant le code a été vérifié et trouvé correct. Maintenant nous pouvons stocker le <code class="language-plaintext highlighter-rouge">$secret</code> avec notre utilisateur dans notre base de données (ou ailleurs) et à chaque fois que lutilisateur commence une nouvelle session nous lui demandons un code généré par lapplication dauthentification de son choix. Tout ce que nous avons à faire est dappeler <code class="language-plaintext highlighter-rouge">verifyCode()</code> à nouveau avec le secret partagé et le code entré et nous savons si lutilisateur est légitime ou non.</p>
<p>Aussi simple que 1-2-3.</p>
<p>Tout ce dont nous avons besoin, cest de 3 méthodes et dun constructeur :</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">function</span> <span class="n">__construct</span><span class="p">(</span>
<span class="nv">$issuer</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span>
<span class="nv">$digits</span> <span class="o">=</span> <span class="mi">6</span><span class="p">,</span>
<span class="nv">$period</span> <span class="o">=</span> <span class="mi">30</span><span class="p">,</span>
<span class="nv">$algorithm</span> <span class="o">=</span> <span class="s1">'sha1'</span><span class="p">,</span>
<span class="kt">RobThree</span><span class="nc">\Auth\Providers\Qr\IQRCodeProvider</span> <span class="nv">$qrcodeprovider</span> <span class="o">=</span> <span class="kc">null</span><span class="p">,</span>
<span class="kt">RobThree</span><span class="nc">\Auth\Providers\Rng\IRNGProvider</span> <span class="nv">$rngprovider</span> <span class="o">=</span> <span class="kc">null</span>
<span class="p">)</span> <span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">createSecret</span><span class="p">(</span><span class="nv">$bits</span> <span class="o">=</span> <span class="mi">80</span><span class="p">,</span> <span class="nv">$requirecryptosecure</span> <span class="o">=</span> <span class="kc">true</span><span class="p">)</span> <span class="p">:</span> <span class="kt">string</span> <span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">getQRCodeImageAsDataUri</span><span class="p">(</span><span class="nv">$label</span><span class="p">,</span> <span class="nv">$secret</span><span class="p">,</span> <span class="nv">$size</span> <span class="o">=</span> <span class="mi">200</span><span class="p">)</span> <span class="p">:</span> <span class="kt">string</span> <span class="p">;</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">verifyCode</span><span class="p">(</span><span class="nv">$secret</span><span class="p">,</span> <span class="nv">$code</span><span class="p">,</span> <span class="nv">$discrepancy</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="nv">$time</span> <span class="o">=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">:</span> <span class="kt">bool</span> <span class="p">;</span>
</code></pre></div></div>
<h3 id="fournisseurs-de-codes-qr">Fournisseurs de codes QR</h3>
<p>Comme mentionné précédemment, cette bibliothèque est fournie avec cinq fournisseurs de codes QR “intégrés”. Ce chapitre abordera un peu le sujet mais la plupart dentre eux devraient être auto-explicatifs. La classe <code class="language-plaintext highlighter-rouge">TwoFactorAuth</code> accepte un argument <code class="language-plaintext highlighter-rouge">$qrcodeprovider</code> qui vous permet de spécifier un fournisseur de QR-code intégré ou personnalisé. Les cinq fournisseurs intégrés font une simple requête HTTP pour récupérer une image en utilisant cURL et implémentent linterface <a href="lib/Providers/Qr/IQRCodeProvider.php"><code class="language-plaintext highlighter-rouge">IQRCodeProvider</code></a> qui est tout ce que vous devez implémenter pour écrire votre propre fournisseur de code QR.</p>
<p>Le fournisseur par défaut est le <a href="lib/Providers/Qr/QRServerProvider.php"><code class="language-plaintext highlighter-rouge">QRServerProvider</code></a> qui utilise lAPI <a href="http://goqr.me/api/doc/create-qr-code/">goqr.me</a> pour rendre les codes QR. Ensuite, nous avons le <a href="lib/Providers/Qr/ImageChartsQRCodeProvider.php"><code class="language-plaintext highlighter-rouge">ImageChartsQRCodeProvider</code></a> qui utilise le <a href="https://image-charts.com">remplacement de Google Image Charts par image-charts.com</a> pour afficher les codes QR et le <a href="lib/Providers/Qr/QRicketProvider.php"><code class="language-plaintext highlighter-rouge">QRicketProvider</code></a> qui utilise l<a href="http://qrickit.com/qrickit_apps/qrickit_api.php">API QRickit</a>. Ces trois fournisseurs héritent tous dune classe de base commune (abstraite) appelée <a href="lib/Providers/Qr/BaseHTTPQRCodeProvider.php"><code class="language-plaintext highlighter-rouge">BaseHTTPQRCodeProvider</code></a> car ils partagent tous les trois la même fonctionnalité : récupérer une image dun tiers via HTTP. Enfin, nous avons <a href="lib/Providers/Qr/EndroidQrCodeProvider.php"><code class="language-plaintext highlighter-rouge">EndroidQrCodeProvider</code></a>, <a href="lib/Providers/Qr/EndroidQrCodeWithLogoProvider. php"><code class="language-plaintext highlighter-rouge">EndroidQrCodeWithLogoProvider</code></a> et <a href="lib/Providers/Qr/BaconQrCodeProvider.php"><code class="language-plaintext highlighter-rouge">BaconQrCodeProvider</code></a> qui nécessitent linstallation dune dépendance optionnelle pour être utilisées (voir la section Exigences ci-dessus), mais qui génèrent les codes QR localement. Les cinq classes ont des constructeurs qui vous permettent de modifier certains paramètres et la plupart des arguments, sinon tous, devraient parler deux-mêmes. Si vous nêtes pas sûr des valeurs prises en charge, cliquez sur les liens dans ce paragraphe pour obtenir de la documentation sur les API utilisées par ces classes.</p>
<p>Si vous naimez pas une des classes intégrées parce que vous ne voulez pas dépendre de ressources externes par exemple ou parce que vous êtes paranoïaque à lidée denvoyer le secret TOTP à ces tiers (ce qui leur est inutile puisquils manquent <em>au moins un</em> autre facteur dans le [processus MFA] (http://en.wikipedia.org/wiki/Multi-factor_authentication)), nhésitez pas à implémenter votre propre classe. Linterface <code class="language-plaintext highlighter-rouge">IQRCodeProvider</code> ne pourrait pas être plus simple. Tout ce que vous avez à faire est dimplémenter 2 méthodes :</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">getMimeType</span><span class="p">();</span>
<span class="nf">getQRCodeImage</span><span class="p">(</span><span class="nv">$qrtext</span><span class="p">,</span> <span class="nv">$size</span><span class="p">);</span>
</code></pre></div></div>
<p>La méthode <code class="language-plaintext highlighter-rouge">getMimeType()</code> doit retourner le <a href="http://en.wikipedia.org/wiki/Internet_media_type">MIME type</a> de limage qui est retournée par notre implémentation de <code class="language-plaintext highlighter-rouge">getQRCodeImage()</code>. Dans cet exemple, il sagit simplement de <code class="language-plaintext highlighter-rouge">image/png</code>. Deux arguments sont passés à la méthode <code class="language-plaintext highlighter-rouge">getQRCodeImage()</code> : <code class="language-plaintext highlighter-rouge">$qrtext</code> et <code class="language-plaintext highlighter-rouge">$size</code>. Ce dernier, <code class="language-plaintext highlighter-rouge">$size</code>, est simplement la largeur/hauteur en pixels de limage souhaitée par lappelant. Le premier, <code class="language-plaintext highlighter-rouge">$qrtext</code>, est le texte qui doit être encodé dans le code QR. Un exemple dun tel texte serait :</p>
<p><code class="language-plaintext highlighter-rouge">otpauth://totp/LABEL:alice@google.com?secret=JBSWY3DPEHPK3PXP&amp;issuer=ISSUER</code></p>
<p>Il suffit de renvoyer le QR-code sous forme de données dimage binaires et le tour est joué. Toutes les parties du <code class="language-plaintext highlighter-rouge">$qrtext</code> ont été échappées pour vous (mais notez : vous <em>pourrez</em> avoir besoin déchapper le <code class="language-plaintext highlighter-rouge">$qrtext</code> entier une fois de plus quand vous passerez les données à un autre serveur comme argument GET).</p>
<p>Voyons si nous pouvons utiliser <a href="http://phpqrcode.sourceforge.net/">PHP QR Code</a> pour implémenter notre propre fournisseur daccès, personnalisé, sans tiers autorisé. Nous commençons par télécharger le <a href="https://github.com/t0k4rt/phpqrcode/blob/master/phpqrcode.php">fichier (unique) requis</a> et le placer dans le répertoire où se trouve <code class="language-plaintext highlighter-rouge">TwoFactorAuth.php</code>. Maintenant, implémentons le fournisseur : créez un autre fichier nommé <code class="language-plaintext highlighter-rouge">myprovider.php</code> dans le répertoire <code class="language-plaintext highlighter-rouge">Providers\Qr</code> et collez-y ce contenu :</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>
<span class="k">require_once</span> <span class="s1">'../../phpqrcode.php'</span><span class="p">;</span> <span class="c1">// Yeah, we're gonna need that</span>
<span class="kn">namespace</span> <span class="nn">RobThree\Auth\Providers\Qr</span><span class="p">;</span>
<span class="kd">class</span> <span class="nc">MyProvider</span> <span class="kd">implements</span> <span class="nc">IQRCodeProvider</span> <span class="p">{</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">getMimeType</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="s1">'image/png'</span><span class="p">;</span> <span class="c1">// This provider only returns PNG's</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">function</span> <span class="n">getQRCodeImage</span><span class="p">(</span><span class="nv">$qrtext</span><span class="p">,</span> <span class="nv">$size</span><span class="p">)</span> <span class="p">{</span>
<span class="nb">ob_start</span><span class="p">();</span> <span class="c1">// 'Catch' QRCode's output</span>
<span class="nc">QRCode</span><span class="o">::</span><span class="nf">png</span><span class="p">(</span><span class="nv">$qrtext</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="no">QR_ECLEVEL_L</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span> <span class="c1">// We ignore $size and set it to 3</span>
<span class="c1">// since phpqrcode doesn't support</span>
<span class="c1">// a size in pixels...</span>
<span class="nv">$result</span> <span class="o">=</span> <span class="nb">ob_get_contents</span><span class="p">();</span> <span class="c1">// 'Catch' QRCode's output</span>
<span class="nb">ob_end_clean</span><span class="p">();</span> <span class="c1">// Cleanup</span>
<span class="k">return</span> <span class="nv">$result</span><span class="p">;</span> <span class="c1">// Return image</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Cest fini. Nous avons terminé ! Nous avons implémenté notre propre fournisseur (avec laide de PHP QR Code). Plus de dépendances externes, plus de latences inutiles. Maintenant, nous allons <em>utiliser</em> notre fournisseur :</p>
<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>
<span class="nv">$mp</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RobThree\Auth\Providers\Qr\MyProvider</span><span class="p">()</span> <span class="p">;</span>
<span class="nv">$tfa</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">RobThree\Auth\TwoFactorAuth</span><span class="p">(</span><span class="s1">'My Company'</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="s1">'sha1'</span><span class="p">,</span> <span class="nv">$mp</span><span class="p">)</span> <span class="p">;</span>
<span class="nv">$secret</span> <span class="o">=</span> <span class="nv">$tfa</span><span class="o">-&gt;</span><span class="nf">createSecret</span><span class="p">()</span> <span class="p">;</span>
<span class="cp">?&gt;</span>
<span class="nt">&lt;p&gt;&lt;img</span> <span class="na">src=</span><span class="s">"</span><span class="cp">&lt;?php</span> <span class="k">echo</span> <span class="nv">$tfa</span><span class="o">-&gt;</span><span class="nf">getQRCodeImageAsDataUri</span><span class="p">(</span><span class="s1">'Bob Ross'</span><span class="p">,</span> <span class="nv">$secret</span><span class="p">)</span> <span class="p">;</span> <span class="cp">?&gt;</span><span class="s">"</span><span class="nt">&gt;&lt;/p&gt;</span>
</code></pre></div></div>
<p>Voilà. On ne pouvait pas faire plus simple.</p>
<h3 id="fournisseurs-de-rng">Fournisseurs de RNG</h3>
<p>Cette bibliothèque est également fournie avec trois fournisseurs RNG “intégrés” (<a href="https://en.wikipedia.org/wiki/Random_number_generation">Random Number Generator</a>). Le fournisseur RNG génère un certain nombre doctets aléatoires et renvoie ces octets sous forme de chaîne. Ces valeurs sont ensuite utilisées pour créer le secret. Par défaut (aucun fournisseur RNG spécifié), TwoFactorAuth essaiera de déterminer le meilleur fournisseur RNG disponible à utiliser. Par défaut, il essaiera dutiliser le <a href="lib/Providers/Rng/CSRNGProvider.php"><code class="language-plaintext highlighter-rouge">CSRNGProvider</code></a> pour PHP7+ ou le <a href="lib/Providers/Rng/MCryptRNGProvider. php"><code class="language-plaintext highlighter-rouge">MCryptRNGProvider</code></a> ; si ce dernier nest pas disponible/pris en charge pour une raison quelconque, il essaiera dutiliser le <a href="lib/Providers/Rng/OpenSSLRNGProvider.php"><code class="language-plaintext highlighter-rouge">OpenSSLRNGProvider</code></a> et si celui-ci nest pas non plus disponible/pris en charge, il essaiera dutiliser le dernier fournisseur de RNG : <a href="lib/Providers/Rng/HashRNGProvider.php"><code class="language-plaintext highlighter-rouge">HashRNGProvider</code></a>. Chacun de ces fournisseurs utilise sa propre méthode pour générer une séquence aléatoire doctets. Les trois premiers (<code class="language-plaintext highlighter-rouge">CSRNGProvider</code>, <code class="language-plaintext highlighter-rouge">OpenSSLRNGProvider</code> et <code class="language-plaintext highlighter-rouge">MCryptRNGProvider</code>) renvoient une séquence <a href="https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator">cryptographiquement sûre</a> doctets aléatoires tandis que le <code class="language-plaintext highlighter-rouge">HashRNGProvider</code> renvoie une séquence <strong>non-cryptographiquement sûre</strong>.</p>
<p>Vous pouvez facilement implémenter votre propre <code class="language-plaintext highlighter-rouge">RNGProvider</code> en implémentant simplement linterface <code class="language-plaintext highlighter-rouge">IRNGProvider</code>. Chacun des fournisseurs de RNG “intégrés” possède des arguments de construction qui vous permettent de modifier certains des paramètres à utiliser lors de la création des octets aléatoires, comme la source à utiliser (<code class="language-plaintext highlighter-rouge">MCryptRNGProvider</code>) ou lalgorithme de hachage (<code class="language-plaintext highlighter-rouge">HashRNGProvider</code>). Je vous encourage à jeter un coup doeil à certains des fournisseurs de RNG intégrés](lib/Providers/Rng) pour plus de détails et à linterface <a href="lib/Providers/Rng/IRNGProvider.php"><code class="language-plaintext highlighter-rouge">IRNGProvider</code></a>.</p>
<h3 id="fournisseurs-de-temps">Fournisseurs de temps</h3>
<p>Un autre ensemble de fournisseurs dans cette bibliothèque sont les fournisseurs de temps ; cette bibliothèque fournit trois fournisseurs intégrés.</p>
<ol>
<li>Le fournisseur de temps par défaut est le <a href="lib/Providers/Time/LocalMachineTimeProvider.php"><code class="language-plaintext highlighter-rouge">LocalMachineTimeProvider</code></a> ; ce fournisseur retourne simplement la sortie de <code class="language-plaintext highlighter-rouge">Time()</code> et est <em>hautement recommandé</em> comme fournisseur par défaut.</li>
<li>Le <a href="lib/Providers/Time/HttpTimeProvider.php"><code class="language-plaintext highlighter-rouge">HttpTimeProvider</code></a> exécute une requête <code class="language-plaintext highlighter-rouge">HEAD</code> contre un serveur web donné (par défaut : google.com) et essaie dextraire len-tête <code class="language-plaintext highlighter-rouge">Date:</code>-HTTP et retourne sa date. Dautres url/domaines peuvent être utilisés en spécifiant lurl dans le constructeur.</li>
<li>Le dernier fournisseur de temps est le <a href="lib/Providers/Time/NTPTimeProvider.php"><code class="language-plaintext highlighter-rouge">NTPTimeProvider</code></a> qui fait une requête NTP à un serveur NTP spécifié.</li>
</ol>
<p>Vous pouvez facilement implémenter votre propre <code class="language-plaintext highlighter-rouge">TimeProvider</code> en implémentant simplement linterface <code class="language-plaintext highlighter-rouge">ITimeProvider</code>.</p>
<p>Pour ce qui est de la raison pour laquelle ces fournisseurs de temps sont implémentés, ils permettent à la bibliothèque TwoFactorAuth de sassurer que lheure de lhôte est correcte (ou plutôt : avec une marge). Vous pouvez utiliser la méthode <code class="language-plaintext highlighter-rouge">ensureCorrectTime()</code> pour vous assurer que lheure de lhôte est correcte. Par défaut, cette méthode va comparer lheure de lhôte (retournée par lappel de <code class="language-plaintext highlighter-rouge">time()</code> sur le <code class="language-plaintext highlighter-rouge">LocalMachineTimeProvider</code>) avec les <code class="language-plaintext highlighter-rouge">NTPTimeProvider</code> et <code class="language-plaintext highlighter-rouge">HttpTimeProvider</code> par défaut. Vous pouvez passer un tableau de <code class="language-plaintext highlighter-rouge">ITimeProvider</code>s pour changer cela et spécifier la <code class="language-plaintext highlighter-rouge">l'indulgence</code> (deuxième argument) autorisée (par défaut : 5 secondes). La méthode sera lancée si le fournisseur de temps de TwoFactorAuth (qui peut être nimporte quel <code class="language-plaintext highlighter-rouge">ITimeProvider</code>, voir constructeur) diffère de plus de la quantité donnée de secondes de nimporte lequel des <code class="language-plaintext highlighter-rouge">ITimeProviders</code> donnés. Nous vous conseillons dappeler cette méthode avec parcimonie lorsque vous vous appuyez sur des tiers (ce que font le <code class="language-plaintext highlighter-rouge">HttpTimeProvider</code> et le <code class="language-plaintext highlighter-rouge">NTPTimeProvider</code>) ou, si vous avez besoin de vous assurer que lheure est correcte sur une base (très) régulière, dimplémenter un <code class="language-plaintext highlighter-rouge">ITimeProvider</code> qui soit plus efficace que les <code class="language-plaintext highlighter-rouge">intégrés' (comme utiliser un signal GPS). La méthode </code>ensureCorrectTime()` est surtout utilisée pour sassurer que le serveur est configuré correctement.</p>
<h3 id="intégrations">Intégrations</h3>
<ul>
<li>CakePHP 3](https://github.com/andrej-griniuk/cakephp-two-factor-auth)</li>
</ul>
<h3 id="licence">Licence</h3>
<p>Sous licence MIT. Voir <a href="https://raw.githubusercontent.com/RobThree/TwoFactorAuth/master/LICENSE">LICENSE</a> pour plus de détails.</p>
<p>Logo / icône](http://www.iconmay.com/Simple/Travel_and_Tourism_Part_2/luggage_lock_safety_baggage_keys_cylinder_lock_hotel_travel_tourism_luggage_lock_icon_465) sous CC0 1.0 Universal (CC0 1.0) Dédicace du domaine public (<a href="http://riii.nl/tm7ap">Page archivée</a>)</p>
</div>
<div class="d-print-none"><footer class="article__footer"><meta itemprop="dateModified" content="2021-03-17T00:00:00+01:00"><!-- start custom article footer snippet -->
<!-- end custom article footer snippet -->
<!--
<div align="right"><a type="application/rss+xml" href="/feed.xml" title="S'abonner"><i class="fa fa-rss fa-2x"></i></a>
&emsp;</div>
-->
</footer>
<div class="article__section-navigator clearfix"><div class="previous"><span>PRÉCÉDENT</span><a href="/2021/03/08/PC1-ArchLinux-XFCE-ASUS_H110M_A-nvme_1To.html">PC1 Ordinateur Bureau ArchLinux xfce - NVME 1To</a></div><div class="next"><span>SUIVANT</span><a href="/2021/03/24/A20-Olinuxino-Domoticz-logiciel-de-gestion-et-de-controle-domotique.html">A20-Olinuxino - Domoticz logiciel de gestion et de contrôle domotique</a></div></div></div>
</div>
<script>(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
$(function() {
var $this ,$scroll;
var $articleContent = $('.js-article-content');
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
var scroll = hasSidebar ? '.js-page-main' : 'html, body';
$scroll = $(scroll);
$articleContent.find('.highlight').each(function() {
$this = $(this);
$this.attr('data-lang', $this.find('code').attr('data-lang'));
});
$articleContent.find('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]').each(function() {
$this = $(this);
$this.append($('<a class="anchor d-print-none" aria-hidden="true"></a>').html('<i class="fas fa-anchor"></i>'));
});
$articleContent.on('click', '.anchor', function() {
$scroll.scrollToAnchor('#' + $(this).parent().attr('id'), 400);
});
});
});
})();
</script>
</div><section class="page__comments d-print-none"></section></article><!-- start custom main bottom snippet -->
<!-- end custom main bottom snippet -->
</div>
</div></div></div></div>
</div><script>(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
var $body = $('body'), $window = $(window);
var $pageRoot = $('.js-page-root'), $pageMain = $('.js-page-main');
var activeCount = 0;
function modal(options) {
var $root = this, visible, onChange, hideWhenWindowScroll = false;
var scrollTop;
function setOptions(options) {
var _options = options || {};
visible = _options.initialVisible === undefined ? false : show;
onChange = _options.onChange;
hideWhenWindowScroll = _options.hideWhenWindowScroll;
}
function init() {
setState(visible);
}
function setState(isShow) {
if (isShow === visible) {
return;
}
visible = isShow;
if (visible) {
activeCount++;
scrollTop = $(window).scrollTop() || $pageMain.scrollTop();
$root.addClass('modal--show');
$pageMain.scrollTop(scrollTop);
activeCount === 1 && ($pageRoot.addClass('show-modal'), $body.addClass('of-hidden'));
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.on('scroll', hide);
$window.on('keyup', handleKeyup);
} else {
activeCount > 0 && activeCount--;
$root.removeClass('modal--show');
$window.scrollTop(scrollTop);
activeCount === 0 && ($pageRoot.removeClass('show-modal'), $body.removeClass('of-hidden'));
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.off('scroll', hide);
$window.off('keyup', handleKeyup);
}
onChange && onChange(visible);
}
function show() {
setState(true);
}
function hide() {
setState(false);
}
function handleKeyup(e) {
// Char Code: 27 ESC
if (e.which === 27) {
hide();
}
}
setOptions(options);
init();
return {
show: show,
hide: hide,
$el: $root
};
}
$.fn.modal = modal;
});
})();
</script><div class="modal modal--overflow page__search-modal d-print-none js-page-search-modal"><script>
(function () {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
// search panel
var search = (window.search || (window.search = {}));
var useDefaultSearchBox = window.useDefaultSearchBox === undefined ?
true : window.useDefaultSearchBox ;
var $searchModal = $('.js-page-search-modal');
var $searchToggle = $('.js-search-toggle');
var searchModal = $searchModal.modal({ onChange: handleModalChange, hideWhenWindowScroll: true });
var modalVisible = false;
search.searchModal = searchModal;
var $searchBox = null;
var $searchInput = null;
var $searchClear = null;
function getModalVisible() {
return modalVisible;
}
search.getModalVisible = getModalVisible;
function handleModalChange(visible) {
modalVisible = visible;
if (visible) {
search.onShow && search.onShow();
useDefaultSearchBox && $searchInput[0] && $searchInput[0].focus();
} else {
search.onShow && search.onHide();
useDefaultSearchBox && $searchInput[0] && $searchInput[0].blur();
setTimeout(function() {
useDefaultSearchBox && ($searchInput.val(''), $searchBox.removeClass('not-empty'));
search.clear && search.clear();
window.pageAsideAffix && window.pageAsideAffix.refresh();
}, 400);
}
}
$searchToggle.on('click', function() {
modalVisible ? searchModal.hide() : searchModal.show();
});
// Char Code: 83 S, 191 /
$(window).on('keyup', function(e) {
if (!modalVisible && !window.isFormElement(e.target || e.srcElement) && (e.which === 83 || e.which === 191)) {
modalVisible || searchModal.show();
}
});
if (useDefaultSearchBox) {
$searchBox = $('.js-search-box');
$searchInput = $searchBox.children('input');
$searchClear = $searchBox.children('.js-icon-clear');
search.getSearchInput = function() {
return $searchInput.get(0);
};
search.getVal = function() {
return $searchInput.val();
};
search.setVal = function(val) {
$searchInput.val(val);
};
$searchInput.on('focus', function() {
$(this).addClass('focus');
});
$searchInput.on('blur', function() {
$(this).removeClass('focus');
});
$searchInput.on('input', window.throttle(function() {
var val = $(this).val();
if (val === '' || typeof val !== 'string') {
search.clear && search.clear();
} else {
$searchBox.addClass('not-empty');
search.onInputNotEmpty && search.onInputNotEmpty(val);
}
}, 400));
$searchClear.on('click', function() {
$searchInput.val(''); $searchBox.removeClass('not-empty');
search.clear && search.clear();
});
}
});
})();
</script><div class="search search--dark">
<div class="main">
<div class="search__header">Recherche</div>
<div class="search-bar">
<div class="search-box js-search-box">
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
<input id="search-input" type="text" />
<div class="search-box__icon-clear js-icon-clear">
<a><i class="fas fa-times"></i></a>
</div>
</div>
<button class="button button--theme-dark button--pill search__cancel js-search-toggle">
Annuler</button>
</div>
<div id="results-container" class="search-result js-search-result"></div>
</div>
</div>
<!-- Script pointing to search-script.js -->
<script>/*!
* Simple-Jekyll-Search
* Copyright 2015-2020, Christian Fei
* Licensed under the MIT License.
*/
(function(){
'use strict'
var _$Templater_7 = {
compile: compile,
setOptions: setOptions
}
const options = {}
options.pattern = /\{(.*?)\}/g
options.template = ''
options.middleware = function () {}
function setOptions (_options) {
options.pattern = _options.pattern || options.pattern
options.template = _options.template || options.template
if (typeof _options.middleware === 'function') {
options.middleware = _options.middleware
}
}
function compile (data) {
return options.template.replace(options.pattern, function (match, prop) {
const value = options.middleware(prop, data[prop], options.template)
if (typeof value !== 'undefined') {
return value
}
return data[prop] || match
})
}
'use strict';
function fuzzysearch (needle, haystack) {
var tlen = haystack.length;
var qlen = needle.length;
if (qlen > tlen) {
return false;
}
if (qlen === tlen) {
return needle === haystack;
}
outer: for (var i = 0, j = 0; i < qlen; i++) {
var nch = needle.charCodeAt(i);
while (j < tlen) {
if (haystack.charCodeAt(j++) === nch) {
continue outer;
}
}
return false;
}
return true;
}
var _$fuzzysearch_1 = fuzzysearch;
'use strict'
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
function FuzzySearchStrategy () {
this.matches = function (string, crit) {
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
}
}
'use strict'
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
function LiteralSearchStrategy () {
this.matches = function (str, crit) {
if (!str) return false
str = str.trim().toLowerCase()
crit = crit.trim().toLowerCase()
return crit.split(' ').filter(function (word) {
return str.indexOf(word) >= 0
}).length === crit.split(' ').length
}
}
'use strict'
var _$Repository_4 = {
put: put,
clear: clear,
search: search,
setOptions: __setOptions_4
}
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
function NoSort () {
return 0
}
const data = []
let opt = {}
opt.fuzzy = false
opt.limit = 10
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
opt.sort = NoSort
opt.exclude = []
function put (data) {
if (isObject(data)) {
return addObject(data)
}
if (isArray(data)) {
return addArray(data)
}
return undefined
}
function clear () {
data.length = 0
return data
}
function isObject (obj) {
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
}
function isArray (obj) {
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
}
function addObject (_data) {
data.push(_data)
return data
}
function addArray (_data) {
const added = []
clear()
for (let i = 0, len = _data.length; i < len; i++) {
if (isObject(_data[i])) {
added.push(addObject(_data[i]))
}
}
return added
}
function search (crit) {
if (!crit) {
return []
}
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
}
function __setOptions_4 (_opt) {
opt = _opt || {}
opt.fuzzy = _opt.fuzzy || false
opt.limit = _opt.limit || 10
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
opt.sort = _opt.sort || NoSort
opt.exclude = _opt.exclude || []
}
function findMatches (data, crit, strategy, opt) {
const matches = []
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
const match = findMatchesInObject(data[i], crit, strategy, opt)
if (match) {
matches.push(match)
}
}
return matches
}
function findMatchesInObject (obj, crit, strategy, opt) {
for (const key in obj) {
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
return obj
}
}
}
function isExcluded (term, excludedTerms) {
for (let i = 0, len = excludedTerms.length; i < len; i++) {
const excludedTerm = excludedTerms[i]
if (new RegExp(excludedTerm).test(term)) {
return true
}
}
return false
}
/* globals ActiveXObject:false */
'use strict'
var _$JSONLoader_2 = {
load: load
}
function load (location, callback) {
const xhr = getXHR()
xhr.open('GET', location, true)
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
xhr.send()
}
function createStateChangeListener (xhr, callback) {
return function () {
if (xhr.readyState === 4 && xhr.status === 200) {
try {
callback(null, JSON.parse(xhr.responseText))
} catch (err) {
callback(err, null)
}
}
}
}
function getXHR () {
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
}
'use strict'
var _$OptionsValidator_3 = function OptionsValidator (params) {
if (!validateParams(params)) {
throw new Error('-- OptionsValidator: required options missing')
}
if (!(this instanceof OptionsValidator)) {
return new OptionsValidator(params)
}
const requiredOptions = params.required
this.getRequiredOptions = function () {
return requiredOptions
}
this.validate = function (parameters) {
const errors = []
requiredOptions.forEach(function (requiredOptionName) {
if (typeof parameters[requiredOptionName] === 'undefined') {
errors.push(requiredOptionName)
}
})
return errors
}
function validateParams (params) {
if (!params) {
return false
}
return typeof params.required !== 'undefined' && params.required instanceof Array
}
}
'use strict'
var _$utils_9 = {
merge: merge,
isJSON: isJSON
}
function merge (defaultParams, mergeParams) {
const mergedOptions = {}
for (const option in defaultParams) {
mergedOptions[option] = defaultParams[option]
if (typeof mergeParams[option] !== 'undefined') {
mergedOptions[option] = mergeParams[option]
}
}
return mergedOptions
}
function isJSON (json) {
try {
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
return true
}
return false
} catch (err) {
return false
}
}
var _$src_8 = {};
(function (window) {
'use strict'
let options = {
searchInput: null,
resultsContainer: null,
json: [],
success: Function.prototype,
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
templateMiddleware: Function.prototype,
sortMiddleware: function () {
return 0
},
noResultsText: 'No results found',
limit: 10,
fuzzy: false,
debounceTime: null,
exclude: []
}
let debounceTimerHandle
const debounce = function (func, delayMillis) {
if (delayMillis) {
clearTimeout(debounceTimerHandle)
debounceTimerHandle = setTimeout(func, delayMillis)
} else {
func.call()
}
}
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
/* removed: const _$Templater_7 = require('./Templater') */;
/* removed: const _$Repository_4 = require('./Repository') */;
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
const optionsValidator = _$OptionsValidator_3({
required: requiredOptions
})
/* removed: const _$utils_9 = require('./utils') */;
window.SimpleJekyllSearch = function (_options) {
const errors = optionsValidator.validate(_options)
if (errors.length > 0) {
throwError('You must specify the following required options: ' + requiredOptions)
}
options = _$utils_9.merge(options, _options)
_$Templater_7.setOptions({
template: options.searchResultTemplate,
middleware: options.templateMiddleware
})
_$Repository_4.setOptions({
fuzzy: options.fuzzy,
limit: options.limit,
sort: options.sortMiddleware,
exclude: options.exclude
})
if (_$utils_9.isJSON(options.json)) {
initWithJSON(options.json)
} else {
initWithURL(options.json)
}
const rv = {
search: search
}
typeof options.success === 'function' && options.success.call(rv)
return rv
}
function initWithJSON (json) {
_$Repository_4.put(json)
registerInput()
}
function initWithURL (url) {
_$JSONLoader_2.load(url, function (err, json) {
if (err) {
throwError('failed to get JSON (' + url + ')')
}
initWithJSON(json)
})
}
function emptyResultsContainer () {
options.resultsContainer.innerHTML = ''
}
function appendToResultsContainer (text) {
options.resultsContainer.innerHTML += text
}
function registerInput () {
options.searchInput.addEventListener('input', function (e) {
if (isWhitelistedKey(e.which)) {
emptyResultsContainer()
debounce(function () { search(e.target.value) }, options.debounceTime)
}
})
}
function search (query) {
if (isValidQuery(query)) {
emptyResultsContainer()
render(_$Repository_4.search(query), query)
}
}
function render (results, query) {
const len = results.length
if (len === 0) {
return appendToResultsContainer(options.noResultsText)
}
for (let i = 0; i < len; i++) {
results[i].query = query
appendToResultsContainer(_$Templater_7.compile(results[i]))
}
}
function isValidQuery (query) {
return query && query.length > 0
}
function isWhitelistedKey (key) {
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
}
function throwError (message) {
throw new Error('SimpleJekyllSearch --- ' + message)
}
})(window)
}());
</script>
<!-- Configuration -->
<script>
SimpleJekyllSearch({
searchInput: document.getElementById('search-input'),
resultsContainer: document.getElementById('results-container'),
noResultsText: '<p>Aucun résultat!</p>',
json: '/search.json',
searchResultTemplate: '<li><a href="{url}">{date}&nbsp;{title}</a>&nbsp;(Création {create})</li>'
})
</script>
</div></div>
<script>(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
function scrollToAnchor(anchor, duration, callback) {
var $root = this;
$root.animate({ scrollTop: $(anchor).position().top }, duration, function() {
window.history.replaceState(null, '', window.location.href.split('#')[0] + anchor);
callback && callback();
});
}
$.fn.scrollToAnchor = scrollToAnchor;
});
})();
(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
function affix(options) {
var $root = this, $window = $(window), $scrollTarget, $scroll,
offsetBottom = 0, scrollTarget = window, scroll = window.document, disabled = false, isOverallScroller = true,
rootTop, rootLeft, rootHeight, scrollBottom, rootBottomTop,
hasInit = false, curState;
function setOptions(options) {
var _options = options || {};
_options.offsetBottom && (offsetBottom = _options.offsetBottom);
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
_options.scroll && (scroll = _options.scroll);
_options.disabled !== undefined && (disabled = _options.disabled);
$scrollTarget = $(scrollTarget);
isOverallScroller = window.isOverallScroller($scrollTarget[0]);
$scroll = $(scroll);
}
function preCalc() {
top();
rootHeight = $root.outerHeight();
rootTop = $root.offset().top + (isOverallScroller ? 0 : $scrollTarget.scrollTop());
rootLeft = $root.offset().left;
}
function calc(needPreCalc) {
needPreCalc && preCalc();
scrollBottom = $scroll.outerHeight() - offsetBottom - rootHeight;
rootBottomTop = scrollBottom - rootTop;
}
function top() {
if (curState !== 'top') {
$root.removeClass('fixed').css({
left: 0,
top: 0
});
curState = 'top';
}
}
function fixed() {
if (curState !== 'fixed') {
$root.addClass('fixed').css({
left: rootLeft + 'px',
top: 0
});
curState = 'fixed';
}
}
function bottom() {
if (curState !== 'bottom') {
$root.removeClass('fixed').css({
left: 0,
top: rootBottomTop + 'px'
});
curState = 'bottom';
}
}
function setState() {
var scrollTop = $scrollTarget.scrollTop();
if (scrollTop >= rootTop && scrollTop <= scrollBottom) {
fixed();
} else if (scrollTop < rootTop) {
top();
} else {
bottom();
}
}
function init() {
if(!hasInit) {
var interval, timeout;
calc(true); setState();
// run calc every 100 millisecond
interval = setInterval(function() {
calc();
}, 100);
timeout = setTimeout(function() {
clearInterval(interval);
}, 45000);
window.pageLoad.then(function() {
setTimeout(function() {
clearInterval(interval);
clearTimeout(timeout);
}, 3000);
});
$scrollTarget.on('scroll', function() {
disabled || setState();
});
$window.on('resize', function() {
disabled || (calc(true), setState());
});
hasInit = true;
}
}
setOptions(options);
if (!disabled) {
init();
}
$window.on('resize', window.throttle(function() {
init();
}, 200));
return {
setOptions: setOptions,
refresh: function() {
calc(true, { animation: false }); setState();
}
};
}
$.fn.affix = affix;
});
})();
(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
function toc(options) {
var $root = this, $window = $(window), $scrollTarget, $scroller, $tocUl = $('<ul class="toc toc--ellipsis"></ul>'), $tocLi, $headings, $activeLast, $activeCur,
selectors = 'h1,h2,h3', container = 'body', scrollTarget = window, scroller = 'html, body', disabled = false,
headingsPos, scrolling = false, hasRendered = false, hasInit = false;
function setOptions(options) {
var _options = options || {};
_options.selectors && (selectors = _options.selectors);
_options.container && (container = _options.container);
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
_options.scroller && (scroller = _options.scroller);
_options.disabled !== undefined && (disabled = _options.disabled);
$headings = $(container).find(selectors).filter('[id]');
$scrollTarget = $(scrollTarget);
$scroller = $(scroller);
}
function calc() {
headingsPos = [];
$headings.each(function() {
headingsPos.push(Math.floor($(this).position().top));
});
}
function setState(element, disabled) {
var scrollTop = $scrollTarget.scrollTop(), i;
if (disabled || !headingsPos || headingsPos.length < 1) { return; }
if (element) {
$activeCur = element;
} else {
for (i = 0; i < headingsPos.length; i++) {
if (scrollTop >= headingsPos[i]) {
$activeCur = $tocLi.eq(i);
} else {
$activeCur || ($activeCur = $tocLi.eq(i));
break;
}
}
}
$activeLast && $activeLast.removeClass('active');
($activeLast = $activeCur).addClass('active');
}
function render() {
if(!hasRendered) {
$root.append($tocUl);
$headings.each(function() {
var $this = $(this);
$tocUl.append($('<li></li>').addClass('toc-' + $this.prop('tagName').toLowerCase())
.append($('<a></a>').text($this.text()).attr('href', '#' + $this.prop('id'))));
});
$tocLi = $tocUl.children('li');
$tocUl.on('click', 'a', function(e) {
e.preventDefault();
var $this = $(this);
scrolling = true;
setState($this.parent());
$scroller.scrollToAnchor($this.attr('href'), 400, function() {
scrolling = false;
});
});
}
hasRendered = true;
}
function init() {
var interval, timeout;
if(!hasInit) {
render(); calc(); setState(null, scrolling);
// run calc every 100 millisecond
interval = setInterval(function() {
calc();
}, 100);
timeout = setTimeout(function() {
clearInterval(interval);
}, 45000);
window.pageLoad.then(function() {
setTimeout(function() {
clearInterval(interval);
clearTimeout(timeout);
}, 3000);
});
$scrollTarget.on('scroll', function() {
disabled || setState(null, scrolling);
});
$window.on('resize', window.throttle(function() {
if (!disabled) {
render(); calc(); setState(null, scrolling);
}
}, 100));
}
hasInit = true;
}
setOptions(options);
if (!disabled) {
init();
}
$window.on('resize', window.throttle(function() {
init();
}, 200));
return {
setOptions: setOptions
};
}
$.fn.toc = toc;
});
})();
/*(function () {
})();*/
</script><script>
/* toc must before affix, since affix need to konw toc' height. */(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
var TOC_SELECTOR = window.TEXT_VARIABLES.site.toc.selectors;
window.Lazyload.js(SOURCES.jquery, function() {
var $window = $(window);
var $articleContent = $('.js-article-content');
var $tocRoot = $('.js-toc-root'), $col2 = $('.js-col-aside');
var toc;
var tocDisabled = false;
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
var hasToc = $articleContent.find(TOC_SELECTOR).length > 0;
function disabled() {
return $col2.css('display') === 'none' || !hasToc;
}
tocDisabled = disabled();
toc = $tocRoot.toc({
selectors: TOC_SELECTOR,
container: $articleContent,
scrollTarget: hasSidebar ? '.js-page-main' : null,
scroller: hasSidebar ? '.js-page-main' : null,
disabled: tocDisabled
});
$window.on('resize', window.throttle(function() {
tocDisabled = disabled();
toc && toc.setOptions({
disabled: tocDisabled
});
}, 100));
});
})();
(function() {
var SOURCES = window.TEXT_VARIABLES.sources;
window.Lazyload.js(SOURCES.jquery, function() {
var $window = $(window), $pageFooter = $('.js-page-footer');
var $pageAside = $('.js-page-aside');
var affix;
var tocDisabled = false;
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
affix = $pageAside.affix({
offsetBottom: $pageFooter.outerHeight(),
scrollTarget: hasSidebar ? '.js-page-main' : null,
scroller: hasSidebar ? '.js-page-main' : null,
scroll: hasSidebar ? $('.js-page-main').children() : null,
disabled: tocDisabled
});
$window.on('resize', window.throttle(function() {
affix && affix.setOptions({
disabled: tocDisabled
});
}, 100));
window.pageAsideAffix = affix;
});
})();
</script><!---->
</div>
<script>(function () {
var $root = document.getElementsByClassName('root')[0];
if (window.hasEvent('touchstart')) {
$root.dataset.isTouch = true;
document.addEventListener('touchstart', function(){}, false);
}
})();
</script>
</body>
</html>