Compare commits

...

No commits in common. 'heroku-pages' and 'docs' have entirely different histories.

  1. 9
      .gitignore
  2. 3
      .gitmodules
  3. 13
      LICENSE
  4. 1
      Procfile
  5. 58
      README.md
  6. 13
      Readme.md
  7. 23
      app.json
  8. BIN
      content/assets/images/favicon.png
  9. 20
      content/assets/images/icons/bitbucket.4ebea66e.svg
  10. 18
      content/assets/images/icons/github.a4034fb1.svg
  11. 38
      content/assets/images/icons/gitlab.348cdb3a.svg
  12. 1
      content/assets/javascripts/application.0cf9b500.js
  13. 1
      content/assets/javascripts/lunr/lunr.da.js
  14. 1
      content/assets/javascripts/lunr/lunr.de.js
  15. 1
      content/assets/javascripts/lunr/lunr.du.js
  16. 1
      content/assets/javascripts/lunr/lunr.es.js
  17. 1
      content/assets/javascripts/lunr/lunr.fi.js
  18. 1
      content/assets/javascripts/lunr/lunr.fr.js
  19. 1
      content/assets/javascripts/lunr/lunr.hu.js
  20. 1
      content/assets/javascripts/lunr/lunr.it.js
  21. 1
      content/assets/javascripts/lunr/lunr.jp.js
  22. 1
      content/assets/javascripts/lunr/lunr.multi.js
  23. 1
      content/assets/javascripts/lunr/lunr.no.js
  24. 1
      content/assets/javascripts/lunr/lunr.pt.js
  25. 1
      content/assets/javascripts/lunr/lunr.ro.js
  26. 1
      content/assets/javascripts/lunr/lunr.ru.js
  27. 1
      content/assets/javascripts/lunr/lunr.stemmer.support.js
  28. 1
      content/assets/javascripts/lunr/lunr.sv.js
  29. 1
      content/assets/javascripts/lunr/lunr.tr.js
  30. 1
      content/assets/javascripts/lunr/tinyseg.js
  31. 1
      content/assets/javascripts/modernizr.1aa3b519.js
  32. 2
      content/assets/stylesheets/application-palette.6079476c.css
  33. 2
      content/assets/stylesheets/application.8d40d89b.css
  34. 11
      content/css/custom.css
  35. 401
      content/fishslap/index.html
  36. BIN
      content/img/attack-rabbit.png
  37. BIN
      content/img/attack-rabbits.png
  38. BIN
      content/img/warning.png
  39. 394
      content/index.html
  40. 7
      content/search/lunr.min.js
  41. 1
      content/search/mustache.min.js
  42. 36
      content/search/require.js
  43. 4
      content/search/search-results-template.mustache
  44. 92
      content/search/search.js
  45. 34
      content/search/search_index.json
  46. 390
      content/search/text.js
  47. 387
      content/sillywalk/index.html
  48. 28
      content/sitemap.xml
  49. 10
      docs/css/custom.css
  50. 33
      docs/custom_domains.md
  51. 174
      docs/flask.md
  52. 25
      docs/flask_auth_github.md
  53. 22
      docs/flask_auth_org.md
  54. 27
      docs/flask_auth_other.md
  55. 64
      docs/flask_auth_portions.md
  56. 98
      docs/flask_heroku.md
  57. 42
      docs/flask_local.md
  58. 57
      docs/github.md
  59. 67
      docs/heroku.md
  60. 0
      docs/img/bunny.png
  61. BIN
      docs/img/warnico.png
  62. BIN
      docs/img/warning.png
  63. 92
      docs/index.md
  64. 110
      docs/repo.md
  65. 142
      github.py
  66. 1
      mkdocs-material
  67. 50
      mkdocs.yml
  68. 2
      requirements.txt
  69. 1
      runtime.txt

9
.gitignore vendored

@ -1 +1,8 @@ @@ -1 +1,8 @@
vp/
# ignore mkdocs output/gh-pages branch
site/
# ignore secret branch
secret/
# ignore any makefiles
Makefile

3
.gitmodules vendored

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
[submodule "mkdocs-material"]
path = mkdocs-material
url = https://git.charlesreid1.com/charlesreid1/mkdocs-material.git

13
LICENSE

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 458.835.16.92, May 2018
Copyright (C) 2018 Charles Reid <charles@charlesreid1.com>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

1
Procfile

@ -1 +0,0 @@ @@ -1 +0,0 @@
web: gunicorn github:app --log-file=-

58
README.md

@ -0,0 +1,58 @@ @@ -0,0 +1,58 @@
# github-heroku-attack-rabbits
## What's this business all about, then?
This repository helps you protect your secret pages by (deep breath):
hosting your secret page of static and/or dynamic content using a free Heroku app
running a Python Flask server that uses Flask-Dance to authenticate visitors
with Github which allows you fine-grained access control over your pages based on
user attributes like organization or team membership or even things like how many
repositories a user has or how many vowels are in their username.
Also, did I mention the attack rabbits?
![warning: attack rabbits ahead](docs/img/warning.png)
## Where is everything?
Final pages:
* The finished product (pages on Heroku protected by attack rabbits)
is at [github-heroku-attack-rabbits.herokuapp.com](https://github-heroku-attack-rabbits.herokuapp.com)
* The documentation is at [pages.charlesreid1.com/github-heroku-attack-rabbits](https://pages.charlesreid1.com/github-heroku-attack-rabbits)
Two branches in this repo compose the github-heroku-attack-rabbits documentation:
* (**YOU ARE HERE**) The [docs](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs) branch
contains the files needed to generate the
[github-heroku-attack-rabbits documentation site](https://pages.charlesreid1.com/github-heroku-attack-rabbits).
* The [gh-pages](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/gh-pages) branch
contains the static files generated from the documentation.
The contents of this branch compose the
[github-heroku-attack-rabbits documentation site](https://pages.charlesreid1.com/github-heroku-attack-rabbits).
Two branches illustrate github-heroku-attack-rabbits in practice:
* The [secret](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/secret) branch contains the files needed to create the secret page.
This repository is public, so obviously these aren't *actually* secret,
but in practice this would be in a protected repository.
* The [heroku-pages](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/heroku-pages) branch
contains the content that is actually pushed to Heroku - that is,
the final Flask app.
## Where do I start?
See the [documentation](https://pages.charlesreid1.com/github-heroku-attack-rabbits)
or [docs/index.md](docs/index.md).
## License
This is released under the [WTFPL](LICENSE).

13
Readme.md

@ -1,13 +0,0 @@ @@ -1,13 +0,0 @@
# github-heroku-attack-rabbits: heroku-pages branch
This branch contains the files needed
for heroku to run a flask oauth server
to authenticate Github users.
Files:
* `Procfile` - tells heroku what application to run
* `app.json` - info about the application for heroku
* `github.py` - github authentication application
* `requirements.txt` - how to install what with python
* `runtime.txt` - version of python to use

23
app.json

@ -1,23 +0,0 @@ @@ -1,23 +0,0 @@
{
"name": "Github Heroku Attack Rabbits",
"description": "Protect private pages hosted on Heroku by authenticating with Github using Flask-Dance. Also, attack rabbits.",
"keywords": [
"oauth"
],
"website": "https://pages.charlesreid1.com/github-heroku-attack-rabbits",
"repository": "https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits",
"env": {
"FLASK_SECRET_KEY": {
"description": "A secret key for verifying the integrity of signed cookies.",
"generator": "secret"
},
"GITHUB_OAUTH_CLIENT_ID": {
"description": "The OAuth client ID for your application, assigned by GitHub",
"value": "client-id-goes-here"
},
"GITHUB_OAUTH_CLIENT_SECRET": {
"description": "The OAuth client secret for your application, assigned by GitHub",
"value": "client-secret-goes-here"
}
}
}

BIN
content/assets/images/favicon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 521 B

20
content/assets/images/icons/bitbucket.4ebea66e.svg

@ -1,20 +0,0 @@ @@ -1,20 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="352" height="448"
viewBox="0 0 352 448" id="bitbucket">
<path fill="currentColor" d="M203.75 214.75q2 15.75-12.625 25.25t-27.875
1.5q-9.75-4.25-13.375-14.5t-0.125-20.5 13-14.5q9-4.5 18.125-3t16 8.875
6.875 16.875zM231.5 209.5q-3.5-26.75-28.25-41t-49.25-3.25q-15.75
7-25.125 22.125t-8.625 32.375q1 22.75 19.375 38.75t41.375 14q22.75-2
38-21t12.5-42zM291.25
74q-5-6.75-14-11.125t-14.5-5.5-17.75-3.125q-72.75-11.75-141.5 0.5-10.75
1.75-16.5 3t-13.75 5.5-12.5 10.75q7.5 7 19 11.375t18.375 5.5 21.875
2.875q57 7.25 112 0.25 15.75-2 22.375-3t18.125-5.375 18.75-11.625zM305.5
332.75q-2 6.5-3.875 19.125t-3.5 21-7.125 17.5-14.5 14.125q-21.5
12-47.375 17.875t-50.5 5.5-50.375-4.625q-11.5-2-20.375-4.5t-19.125-6.75-18.25-10.875-13-15.375q-6.25-24-14.25-73l1.5-4
4.5-2.25q55.75 37 126.625 37t126.875-37q5.25 1.5 6 5.75t-1.25 11.25-2
9.25zM350.75 92.5q-6.5 41.75-27.75 163.75-1.25 7.5-6.75 14t-10.875
10-13.625 7.75q-63 31.5-152.5
22-62-6.75-98.5-34.75-3.75-3-6.375-6.625t-4.25-8.75-2.25-8.5-1.5-9.875-1.375-8.75q-2.25-12.5-6.625-37.5t-7-40.375-5.875-36.875-5.5-39.5q0.75-6.5
4.375-12.125t7.875-9.375 11.25-7.5 11.5-5.625 12-4.625q31.25-11.5
78.25-16 94.75-9.25 169 12.5 38.75 11.5 53.75 30.5 4 5 4.125
12.75t-1.375 13.5z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

18
content/assets/images/icons/github.a4034fb1.svg

@ -1,18 +0,0 @@ @@ -1,18 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="416" height="448"
viewBox="0 0 416 448" id="github">
<path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19-18.125
8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19 18.125-8.5
18.125 8.5 10.75 19 3.125 20.5zM320 304q0 10-3.125 20.5t-10.75
19-18.125 8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19
18.125-8.5 18.125 8.5 10.75 19 3.125 20.5zM360
304q0-30-17.25-51t-46.75-21q-10.25 0-48.75 5.25-17.75 2.75-39.25
2.75t-39.25-2.75q-38-5.25-48.75-5.25-29.5 0-46.75 21t-17.25 51q0 22 8
38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0
37.25-1.75t35-7.375 30.5-15 20.25-25.75 8-38.375zM416 260q0 51.75-15.25
82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5-41.75
1.125q-19.5 0-35.5-0.75t-36.875-3.125-38.125-7.5-34.25-12.875-30.25-20.25-21.5-28.75q-15.5-30.75-15.5-82.75
0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25
30.875q36.75-8.75 77.25-8.75 37 0 70 8 26.25-20.5
46.75-30.25t47.25-9.75q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34
99.5z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

38
content/assets/images/icons/gitlab.348cdb3a.svg

@ -1,38 +0,0 @@ @@ -1,38 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500"
viewBox="0 0 500 500" id="gitlab">
<g transform="translate(156.197863, 1.160267)">
<path fill="currentColor"
d="M93.667,473.347L93.667,473.347l90.684-279.097H2.983L93.667,
473.347L93.667,473.347z" />
</g>
<g transform="translate(28.531199, 1.160800)" opacity="0.7">
<path fill="currentColor"
d="M221.333,473.345L130.649,194.25H3.557L221.333,473.345L221.333,
473.345z" />
</g>
<g transform="translate(0.088533, 0.255867)" opacity="0.5">
<path fill="currentColor"
d="M32,195.155L32,195.155L4.441,279.97c-2.513,7.735,0.24,16.21,6.821,
20.99l238.514,173.29 L32,195.155L32,195.155z" />
</g>
<g transform="translate(29.421866, 280.255593)">
<path fill="currentColor"
d="M2.667-84.844h127.092L75.14-252.942c-2.811-8.649-15.047-8.649-17.856,
0L2.667-84.844 L2.667-84.844z" />
</g>
<g transform="translate(247.197860, 1.160800)" opacity="0.7">
<path fill="currentColor"
d="M2.667,473.345L93.351,194.25h127.092L2.667,473.345L2.667,
473.345z" />
</g>
<g transform="translate(246.307061, 0.255867)" opacity="0.5">
<path fill="currentColor"
d="M221.334,195.155L221.334,195.155l27.559,84.815c2.514,7.735-0.24,
16.21-6.821,20.99 L3.557,474.25L221.334,195.155L221.334,195.155z" />
</g>
<g transform="translate(336.973725, 280.255593)">
<path fill="currentColor"
d="M130.667-84.844H3.575l54.618-168.098c2.811-8.649,15.047-8.649,
17.856,0L130.667-84.844 L130.667-84.844z" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

1
content/assets/javascripts/application.0cf9b500.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.da.js

@ -1 +0,0 @@ @@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,i,n;e.da=function(){this.pipeline.reset(),this.pipeline.add(e.da.trimmer,e.da.stopWordFilter,e.da.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.da.stemmer))},e.da.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.da.trimmer=e.trimmerSupport.generateTrimmer(e.da.wordCharacters),e.Pipeline.registerFunction(e.da.trimmer,"trimmer-da"),e.da.stemmer=(r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){var e,n,t,s=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],o=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],a=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],u=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],c=new i;function l(){var e,r=c.limit-c.cursor;c.cursor>=n&&(e=c.limit_backward,c.limit_backward=n,c.ket=c.cursor,c.find_among_b(o,4)?(c.bra=c.cursor,c.limit_backward=e,c.cursor=c.limit-r,c.cursor>c.limit_backward&&(c.cursor--,c.bra=c.cursor,c.slice_del())):c.limit_backward=e)}this.setCurrent=function(e){c.setCurrent(e)},this.getCurrent=function(){return c.getCurrent()},this.stem=function(){var r,i=c.cursor;return function(){var r,i=c.cursor+3;if(n=c.limit,0<=i&&i<=c.limit){for(e=i;;){if(r=c.cursor,c.in_grouping(d,97,248)){c.cursor=r;break}if(c.cursor=r,r>=c.limit)return;c.cursor++}for(;!c.out_grouping(d,97,248);){if(c.cursor>=c.limit)return;c.cursor++}(n=c.cursor)<e&&(n=e)}}(),c.limit_backward=i,c.cursor=c.limit,function(){var e,r;if(c.cursor>=n&&(r=c.limit_backward,c.limit_backward=n,c.ket=c.cursor,e=c.find_among_b(s,32),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del();break;case 2:c.in_grouping_b(u,97,229)&&c.slice_del()}}(),c.cursor=c.limit,l(),c.cursor=c.limit,function(){var e,r,i,t=c.limit-c.cursor;if(c.ket=c.cursor,c.eq_s_b(2,"st")&&(c.bra=c.cursor,c.eq_s_b(2,"ig")&&c.slice_del()),c.cursor=c.limit-t,c.cursor>=n&&(r=c.limit_backward,c.limit_backward=n,c.ket=c.cursor,e=c.find_among_b(a,5),c.limit_backward=r,e))switch(c.bra=c.cursor,e){case 1:c.slice_del(),i=c.limit-c.cursor,l(),c.cursor=c.limit-i;break;case 2:c.slice_from("løs")}}(),c.cursor=c.limit,c.cursor>=n&&(r=c.limit_backward,c.limit_backward=n,c.ket=c.cursor,c.out_grouping_b(d,97,248)?(c.bra=c.cursor,t=c.slice_to(t),c.limit_backward=r,c.eq_v_b(t)&&c.slice_del()):c.limit_backward=r),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}});

1
content/assets/javascripts/lunr/lunr.de.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.du.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.es.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.fi.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.fr.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.hu.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.it.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.jp.js

@ -1 +0,0 @@ @@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.jp=function(){this.pipeline.reset(),this.pipeline.add(e.jp.stopWordFilter,e.jp.stemmer),r?this.tokenizer=e.jp.tokenizer:(e.tokenizer&&(e.tokenizer=e.jp.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.jp.tokenizer))};var t=new e.TinySegmenter;e.jp.tokenizer=function(n){if(!arguments.length||null==n||null==n)return[];if(Array.isArray(n))return n.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(var i=n.toString().toLowerCase().replace(/^\s+/,""),o=i.length-1;o>=0;o--)if(/\S/.test(i.charAt(o))){i=i.substring(0,o+1);break}return t.segment(i).filter(function(e){return!!e}).map(function(t){return r?new e.Token(t):t})},e.jp.stemmer=function(e){return e},e.Pipeline.registerFunction(e.jp.stemmer,"stemmer-jp"),e.jp.wordCharacters="一二三四五六七八九十百千万億兆一-龠々〆ヵヶぁ-んァ-ヴーア-ン゙a-zA-Za-zA-Z0-90-9",e.jp.stopWordFilter=function(t){if(-1===e.jp.stopWordFilter.stopWords.indexOf(r?t.toString():t))return t},e.jp.stopWordFilter=e.generateStopWordFilter("これ それ あれ この その あの ここ そこ あそこ こちら どこ だれ なに なん 何 私 貴方 貴方方 我々 私達 あの人 あのかた 彼女 彼 です あります おります います は が の に を で え から まで より も どの と し それで しかし".split(" ")),e.Pipeline.registerFunction(e.jp.stopWordFilter,"stopWordFilter-jp")}});

1
content/assets/javascripts/lunr/lunr.multi.js

@ -1 +0,0 @@ @@ -1 +0,0 @@
!function(e,i){"function"==typeof define&&define.amd?define(i):"object"==typeof exports?module.exports=i():i()(e.lunr)}(this,function(){return function(e){e.multiLanguage=function(){for(var i=Array.prototype.slice.call(arguments),t=i.join("-"),r="",n=[],s=[],p=0;p<i.length;++p)"en"==i[p]?(r+="\\w",n.unshift(e.stopWordFilter),n.push(e.stemmer),s.push(e.stemmer)):(r+=e[i[p]].wordCharacters,n.unshift(e[i[p]].stopWordFilter),n.push(e[i[p]].stemmer),s.push(e[i[p]].stemmer));var o=e.trimmerSupport.generateTrimmer(r);return e.Pipeline.registerFunction(o,"lunr-multi-trimmer-"+t),n.unshift(o),function(){this.pipeline.reset(),this.pipeline.add.apply(this.pipeline,n),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add.apply(this.searchPipeline,s))}}}});

1
content/assets/javascripts/lunr/lunr.no.js

@ -1 +0,0 @@ @@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,n,i;e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=(r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){var e,i,t=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],o=[new r("dt",-1,-1),new r("vt",-1,-1)],s=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],a=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],m=[119,125,149,1],l=new n;this.setCurrent=function(e){l.setCurrent(e)},this.getCurrent=function(){return l.getCurrent()},this.stem=function(){var r,n,u,d,c=l.cursor;return function(){var r,n=l.cursor+3;if(i=l.limit,0<=n||n<=l.limit){for(e=n;;){if(r=l.cursor,l.in_grouping(a,97,248)){l.cursor=r;break}if(r>=l.limit)return;l.cursor=r+1}for(;!l.out_grouping(a,97,248);){if(l.cursor>=l.limit)return;l.cursor++}(i=l.cursor)<e&&(i=e)}}(),l.limit_backward=c,l.cursor=l.limit,function(){var e,r,n;if(l.cursor>=i&&(r=l.limit_backward,l.limit_backward=i,l.ket=l.cursor,e=l.find_among_b(t,29),l.limit_backward=r,e))switch(l.bra=l.cursor,e){case 1:l.slice_del();break;case 2:n=l.limit-l.cursor,l.in_grouping_b(m,98,122)?l.slice_del():(l.cursor=l.limit-n,l.eq_s_b(1,"k")&&l.out_grouping_b(a,97,248)&&l.slice_del());break;case 3:l.slice_from("er")}}(),l.cursor=l.limit,n=l.limit-l.cursor,l.cursor>=i&&(r=l.limit_backward,l.limit_backward=i,l.ket=l.cursor,l.find_among_b(o,2)?(l.bra=l.cursor,l.limit_backward=r,l.cursor=l.limit-n,l.cursor>l.limit_backward&&(l.cursor--,l.bra=l.cursor,l.slice_del())):l.limit_backward=r),l.cursor=l.limit,l.cursor>=i&&(d=l.limit_backward,l.limit_backward=i,l.ket=l.cursor,(u=l.find_among_b(s,11))?(l.bra=l.cursor,l.limit_backward=d,1==u&&l.slice_del()):l.limit_backward=d),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}});

1
content/assets/javascripts/lunr/lunr.pt.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.ro.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.ru.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/lunr.stemmer.support.js

@ -1 +0,0 @@ @@ -1 +0,0 @@
!function(r,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(r.lunr)}(this,function(){return function(r){r.stemmerSupport={Among:function(r,t,i,s){if(this.toCharArray=function(r){for(var t=r.length,i=new Array(t),s=0;s<t;s++)i[s]=r.charCodeAt(s);return i},!r&&""!=r||!t&&0!=t||!i)throw"Bad Among initialisation: s:"+r+", substring_i: "+t+", result: "+i;this.s_size=r.length,this.s=this.toCharArray(r),this.substring_i=t,this.result=i,this.method=s},SnowballProgram:function(){var r;return{bra:0,ket:0,limit:0,cursor:0,limit_backward:0,setCurrent:function(t){r=t,this.cursor=0,this.limit=t.length,this.limit_backward=0,this.bra=this.cursor,this.ket=this.limit},getCurrent:function(){var t=r;return r=null,t},in_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e<=s&&e>=i&&t[(e-=i)>>3]&1<<(7&e))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&t[(e-=i)>>3]&1<<(7&e))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e>s||e<i)return this.cursor++,!0;if(!(t[(e-=i)>>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e<i)return this.cursor--,!0;if(!(t[(e-=i)>>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor+s)!=i.charCodeAt(s))return!1;return this.cursor+=t,!0},eq_s_b:function(t,i){if(this.cursor-this.limit_backward<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor-t+s)!=i.charCodeAt(s))return!1;return this.cursor-=t,!0},find_among:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=l;m<_.s_size;m++){if(n+l==u){f=-1;break}if(f=r.charCodeAt(n+l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){if(o>=(_=t[s]).s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=(m=t[a]).s_size-1-l;_>=0;_--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-m.s[_])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var m;if(o>=(m=t[s]).s_size){if(this.cursor=n-m.s_size,!m.method)return m.result;var b=m.method();if(this.cursor=n-m.s_size,b)return m.result}if((s=m.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}});

1
content/assets/javascripts/lunr/lunr.sv.js

@ -1 +0,0 @@ @@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r,n,t;e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=(r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){var e,t,i=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],s=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],a=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],o=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],u=[119,127,149],m=new n;this.setCurrent=function(e){m.setCurrent(e)},this.getCurrent=function(){return m.getCurrent()},this.stem=function(){var r,n=m.cursor;return function(){var r,n=m.cursor+3;if(t=m.limit,0<=n||n<=m.limit){for(e=n;;){if(r=m.cursor,m.in_grouping(o,97,246)){m.cursor=r;break}if(m.cursor=r,m.cursor>=m.limit)return;m.cursor++}for(;!m.out_grouping(o,97,246);){if(m.cursor>=m.limit)return;m.cursor++}(t=m.cursor)<e&&(t=e)}}(),m.limit_backward=n,m.cursor=m.limit,function(){var e,r=m.limit_backward;if(m.cursor>=t&&(m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(i,37),m.limit_backward=r,e))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.in_grouping_b(u,98,121)&&m.slice_del()}}(),m.cursor=m.limit,r=m.limit_backward,m.cursor>=t&&(m.limit_backward=t,m.cursor=m.limit,m.find_among_b(s,7)&&(m.cursor=m.limit,m.ket=m.cursor,m.cursor>m.limit_backward&&(m.bra=--m.cursor,m.slice_del())),m.limit_backward=r),m.cursor=m.limit,function(){var e,r;if(m.cursor>=t){if(r=m.limit_backward,m.limit_backward=t,m.cursor=m.limit,m.ket=m.cursor,e=m.find_among_b(a,5))switch(m.bra=m.cursor,e){case 1:m.slice_del();break;case 2:m.slice_from("lös");break;case 3:m.slice_from("full")}m.limit_backward=r}}(),!0}},function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}});

1
content/assets/javascripts/lunr/lunr.tr.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/lunr/tinyseg.js

File diff suppressed because one or more lines are too long

1
content/assets/javascripts/modernizr.1aa3b519.js

File diff suppressed because one or more lines are too long

2
content/assets/stylesheets/application-palette.6079476c.css

File diff suppressed because one or more lines are too long

2
content/assets/stylesheets/application.8d40d89b.css

File diff suppressed because one or more lines are too long

11
content/css/custom.css

@ -1,11 +0,0 @@ @@ -1,11 +0,0 @@
.md-typeset h1 { font-weight: 600; }
.md-typeset h2 { font-weight: 600; }
.md-typeset h3 { font-weight: 600; }
.md-typeset h4 { font-weight: 600; }
body {
background-color: #f2f9f7;
}
div.body {
background-color: #f2f9f7;
}

401
content/fishslap/index.html

@ -1,401 +0,0 @@ @@ -1,401 +0,0 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<link rel="canonical" href="https://github-heroku-attack-rabbits.herokuapp.com/fishslap/">
<meta name="lang:clipboard.copy" content="Copy to clipboard">
<meta name="lang:clipboard.copied" content="Copied to clipboard">
<meta name="lang:search.language" content="en">
<meta name="lang:search.pipeline.stopwords" content="True">
<meta name="lang:search.pipeline.trimmer" content="True">
<meta name="lang:search.result.none" content="No matching documents">
<meta name="lang:search.result.one" content="1 matching document">
<meta name="lang:search.result.other" content="# matching documents">
<meta name="lang:search.tokenizer" content="[\s\-]+">
<link rel="shortcut icon" href="../">
<meta name="generator" content="mkdocs-0.17.3, mkdocs-material-2.7.2">
<title>Fish Slapping - This Is The Secret Site</title>
<link rel="stylesheet" href="../assets/stylesheets/application.8d40d89b.css">
<link rel="stylesheet" href="../assets/stylesheets/application-palette.6079476c.css">
<script src="../assets/javascripts/modernizr.1aa3b519.js"></script>
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Questrial:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Questrial","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="../css/custom.css">
</head>
<body dir="ltr" data-md-color-primary="teal" data-md-color-accent="teal">
<svg class="md-svg">
<defs>
<svg xmlns="http://www.w3.org/2000/svg" width="416" height="448"
viewBox="0 0 416 448" id="github">
<path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19-18.125
8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19 18.125-8.5
18.125 8.5 10.75 19 3.125 20.5zM320 304q0 10-3.125 20.5t-10.75
19-18.125 8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19
18.125-8.5 18.125 8.5 10.75 19 3.125 20.5zM360
304q0-30-17.25-51t-46.75-21q-10.25 0-48.75 5.25-17.75 2.75-39.25
2.75t-39.25-2.75q-38-5.25-48.75-5.25-29.5 0-46.75 21t-17.25 51q0 22 8
38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0
37.25-1.75t35-7.375 30.5-15 20.25-25.75 8-38.375zM416 260q0 51.75-15.25
82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5-41.75
1.125q-19.5 0-35.5-0.75t-36.875-3.125-38.125-7.5-34.25-12.875-30.25-20.25-21.5-28.75q-15.5-30.75-15.5-82.75
0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25
30.875q36.75-8.75 77.25-8.75 37 0 70 8 26.25-20.5
46.75-30.25t47.25-9.75q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34
99.5z" />
</svg>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search" autocomplete="off">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<a href="#the-secret-society-of-the-fish-slapping-dance" tabindex="1" class="md-skip">
Skip to content
</a>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://github-heroku-attack-rabbits.herokuapp.com" title="This Is The Secret Site" class="md-header-nav__button md-logo">
<img src="../img/bunny.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
<span class="md-header-nav__topic">
This Is The Secret Site
</span>
<span class="md-header-nav__topic">
Fish Slapping
</span>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query" data-md-state="active">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset" tabindex="-1">
&#xE5CD;
</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result">
<div class="md-search-result__meta">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
<a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
charlesreid1/github-heroku-attack-rabbits
</div>
</a>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<span class="md-nav__button md-logo">
<img src="../img/bunny.png" width="48" height="48">
</span>
This Is The Secret Site
</label>
<div class="md-nav__source">
<a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
charlesreid1/github-heroku-attack-rabbits
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." title="Index" class="md-nav__link">
Index
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<a href="./" title="Fish Slapping" class="md-nav__link md-nav__link--active">
Fish Slapping
</a>
</li>
<li class="md-nav__item">
<a href="../sillywalk/" title="Silly Walks" class="md-nav__link">
Silly Walks
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="the-secret-society-of-the-fish-slapping-dance">The Secret Society of the Fish Slapping Dance</h1>
<p>Welcome, and congratulations on having an even number of vowels in your Github handle. </p>
<p>And now for something completely expected:</p>
<p><a href="https://www.youtube.com/watch?v=T8XeDvKqI4E">Fish-Slapping Dance</a></p>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href=".." title="Index" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
Index
</span>
</div>
</a>
<a href="../sillywalk/" title="Silly Walks" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Silly Walks
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2018 Charles Reid, released under the <a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs/LICENSE">WTFPL</a>.<br /><br />Many Bothans died to bring us this documentation.<br /><br />
</div>
powered by
<a href="http://www.mkdocs.org">MkDocs</a>
and
<a href="https://squidfunk.github.io/mkdocs-material/">
Material for MkDocs</a>
</div>
</div>
</div>
</footer>
</div>
<script src="../assets/javascripts/application.0cf9b500.js"></script>
<script>app.initialize({version:"0.17.3",url:{base:".."}})</script>
<script src="../search/require.js"></script>
<script src="../search/search.js"></script>
</body>
</html>

BIN
content/img/attack-rabbit.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 598 KiB

BIN
content/img/attack-rabbits.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 621 KiB

BIN
content/img/warning.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

394
content/index.html

@ -1,394 +0,0 @@ @@ -1,394 +0,0 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<link rel="canonical" href="https://github-heroku-attack-rabbits.herokuapp.com/">
<meta name="lang:clipboard.copy" content="Copy to clipboard">
<meta name="lang:clipboard.copied" content="Copied to clipboard">
<meta name="lang:search.language" content="en">
<meta name="lang:search.pipeline.stopwords" content="True">
<meta name="lang:search.pipeline.trimmer" content="True">
<meta name="lang:search.result.none" content="No matching documents">
<meta name="lang:search.result.one" content="1 matching document">
<meta name="lang:search.result.other" content="# matching documents">
<meta name="lang:search.tokenizer" content="[\s\-]+">
<link rel="shortcut icon" href="./">
<meta name="generator" content="mkdocs-0.17.3, mkdocs-material-2.7.2">
<title>This Is The Secret Site</title>
<link rel="stylesheet" href="./assets/stylesheets/application.8d40d89b.css">
<link rel="stylesheet" href="./assets/stylesheets/application-palette.6079476c.css">
<script src="./assets/javascripts/modernizr.1aa3b519.js"></script>
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Questrial:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Questrial","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="./css/custom.css">
</head>
<body dir="ltr" data-md-color-primary="teal" data-md-color-accent="teal">
<svg class="md-svg">
<defs>
<svg xmlns="http://www.w3.org/2000/svg" width="416" height="448"
viewBox="0 0 416 448" id="github">
<path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19-18.125
8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19 18.125-8.5
18.125 8.5 10.75 19 3.125 20.5zM320 304q0 10-3.125 20.5t-10.75
19-18.125 8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19
18.125-8.5 18.125 8.5 10.75 19 3.125 20.5zM360
304q0-30-17.25-51t-46.75-21q-10.25 0-48.75 5.25-17.75 2.75-39.25
2.75t-39.25-2.75q-38-5.25-48.75-5.25-29.5 0-46.75 21t-17.25 51q0 22 8
38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0
37.25-1.75t35-7.375 30.5-15 20.25-25.75 8-38.375zM416 260q0 51.75-15.25
82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5-41.75
1.125q-19.5 0-35.5-0.75t-36.875-3.125-38.125-7.5-34.25-12.875-30.25-20.25-21.5-28.75q-15.5-30.75-15.5-82.75
0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25
30.875q36.75-8.75 77.25-8.75 37 0 70 8 26.25-20.5
46.75-30.25t47.25-9.75q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34
99.5z" />
</svg>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search" autocomplete="off">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<a href="#secret-index" tabindex="1" class="md-skip">
Skip to content
</a>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://github-heroku-attack-rabbits.herokuapp.com" title="This Is The Secret Site" class="md-header-nav__button md-logo">
<img src="./img/bunny.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
<span class="md-header-nav__topic">
This Is The Secret Site
</span>
<span class="md-header-nav__topic">
Index
</span>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query" data-md-state="active">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset" tabindex="-1">
&#xE5CD;
</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result">
<div class="md-search-result__meta">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
<a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
charlesreid1/github-heroku-attack-rabbits
</div>
</a>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<span class="md-nav__button md-logo">
<img src="./img/bunny.png" width="48" height="48">
</span>
This Is The Secret Site
</label>
<div class="md-nav__source">
<a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
charlesreid1/github-heroku-attack-rabbits
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<a href="." title="Index" class="md-nav__link md-nav__link--active">
Index
</a>
</li>
<li class="md-nav__item">
<a href="fishslap/" title="Fish Slapping" class="md-nav__link">
Fish Slapping
</a>
</li>
<li class="md-nav__item">
<a href="sillywalk/" title="Silly Walks" class="md-nav__link">
Silly Walks
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="secret-index">Secret Index</h1>
<p>This is an index of secret pages. Note that this page is not protected.</p>
<p>You will be asked to authenticate when you click the links below.</p>
<p>If your username has an <strong>even number of vowels</strong>, you are a member
of the <a href="fishslap/">Secret Society of the Fish Slappers</a>.
Click the link to log in and proceed, or else be attacked by
the Github-Heroku attack rabbits.</p>
<p>If your username has an <strong>odd number of vowels</strong>, you are a member
of the <a href="sillywalk/">Secret Ministerial Department for Theoretical Silly Walk Studies</a>.
Click the link to log in and proceed, or else be attacked by
the Github-Heroku attack rabbits.</p>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="fishslap/" title="Fish Slapping" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Fish Slapping
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2018 Charles Reid, released under the <a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs/LICENSE">WTFPL</a>.<br /><br />Many Bothans died to bring us this documentation.<br /><br />
</div>
powered by
<a href="http://www.mkdocs.org">MkDocs</a>
and
<a href="https://squidfunk.github.io/mkdocs-material/">
Material for MkDocs</a>
</div>
</div>
</div>
</footer>
</div>
<script src="./assets/javascripts/application.0cf9b500.js"></script>
<script>app.initialize({version:"0.17.3",url:{base:"."}})</script>
<script src="./search/require.js"></script>
<script src="./search/search.js"></script>
</body>
</html>

7
content/search/lunr.min.js vendored

File diff suppressed because one or more lines are too long

1
content/search/mustache.min.js vendored

File diff suppressed because one or more lines are too long

36
content/search/require.js

@ -1,36 +0,0 @@ @@ -1,36 +0,0 @@
/*
RequireJS 2.1.16 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved.
Available via the MIT or new BSD license.
see: http://github.com/jrburke/requirejs for details
*/
var requirejs,require,define;
(function(ba){function G(b){return"[object Function]"===K.call(b)}function H(b){return"[object Array]"===K.call(b)}function v(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+=1);}}function T(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b));d-=1);}}function t(b,c){return fa.call(b,c)}function m(b,c){return t(b,c)&&b[c]}function B(b,c){for(var d in b)if(t(b,d)&&c(b[d],d))break}function U(b,c,d,e){c&&B(c,function(c,g){if(d||!t(b,g))e&&"object"===typeof c&&c&&!H(c)&&!G(c)&&!(c instanceof
RegExp)?(b[g]||(b[g]={}),U(b[g],c,d,e)):b[g]=c});return b}function u(b,c){return function(){return c.apply(b,arguments)}}function ca(b){throw b;}function da(b){if(!b)return b;var c=ba;v(b.split("."),function(b){c=c[b]});return c}function C(b,c,d,e){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"+b);c.requireType=b;c.requireModules=e;d&&(c.originalError=d);return c}function ga(b){function c(a,k,b){var f,l,c,d,e,g,i,p,k=k&&k.split("/"),h=j.map,n=h&&h["*"];if(a){a=a.split("/");l=a.length-1;j.nodeIdCompat&&
Q.test(a[l])&&(a[l]=a[l].replace(Q,""));"."===a[0].charAt(0)&&k&&(l=k.slice(0,k.length-1),a=l.concat(a));l=a;for(c=0;c<l.length;c++)if(d=l[c],"."===d)l.splice(c,1),c-=1;else if(".."===d&&!(0===c||1==c&&".."===l[2]||".."===l[c-1])&&0<c)l.splice(c-1,2),c-=2;a=a.join("/")}if(b&&h&&(k||n)){l=a.split("/");c=l.length;a:for(;0<c;c-=1){e=l.slice(0,c).join("/");if(k)for(d=k.length;0<d;d-=1)if(b=m(h,k.slice(0,d).join("/")))if(b=m(b,e)){f=b;g=c;break a}!i&&(n&&m(n,e))&&(i=m(n,e),p=c)}!f&&i&&(f=i,g=p);f&&(l.splice(0,
g,f),a=l.join("/"))}return(f=m(j.pkgs,a))?f:a}function d(a){z&&v(document.getElementsByTagName("script"),function(k){if(k.getAttribute("data-requiremodule")===a&&k.getAttribute("data-requirecontext")===i.contextName)return k.parentNode.removeChild(k),!0})}function e(a){var k=m(j.paths,a);if(k&&H(k)&&1<k.length)return k.shift(),i.require.undef(a),i.makeRequire(null,{skipMap:!0})([a]),!0}function n(a){var k,c=a?a.indexOf("!"):-1;-1<c&&(k=a.substring(0,c),a=a.substring(c+1,a.length));return[k,a]}function p(a,
k,b,f){var l,d,e=null,g=k?k.name:null,j=a,p=!0,h="";a||(p=!1,a="_@r"+(K+=1));a=n(a);e=a[0];a=a[1];e&&(e=c(e,g,f),d=m(r,e));a&&(e?h=d&&d.normalize?d.normalize(a,function(a){return c(a,g,f)}):-1===a.indexOf("!")?c(a,g,f):a:(h=c(a,g,f),a=n(h),e=a[0],h=a[1],b=!0,l=i.nameToUrl(h)));b=e&&!d&&!b?"_unnormalized"+(O+=1):"";return{prefix:e,name:h,parentMap:k,unnormalized:!!b,url:l,originalName:j,isDefine:p,id:(e?e+"!"+h:h)+b}}function s(a){var k=a.id,b=m(h,k);b||(b=h[k]=new i.Module(a));return b}function q(a,
k,b){var f=a.id,c=m(h,f);if(t(r,f)&&(!c||c.defineEmitComplete))"defined"===k&&b(r[f]);else if(c=s(a),c.error&&"error"===k)b(c.error);else c.on(k,b)}function w(a,b){var c=a.requireModules,f=!1;if(b)b(a);else if(v(c,function(b){if(b=m(h,b))b.error=a,b.events.error&&(f=!0,b.emit("error",a))}),!f)g.onError(a)}function x(){R.length&&(ha.apply(A,[A.length,0].concat(R)),R=[])}function y(a){delete h[a];delete V[a]}function F(a,b,c){var f=a.map.id;a.error?a.emit("error",a.error):(b[f]=!0,v(a.depMaps,function(f,
d){var e=f.id,g=m(h,e);g&&(!a.depMatched[d]&&!c[e])&&(m(b,e)?(a.defineDep(d,r[e]),a.check()):F(g,b,c))}),c[f]=!0)}function D(){var a,b,c=(a=1E3*j.waitSeconds)&&i.startTime+a<(new Date).getTime(),f=[],l=[],g=!1,h=!0;if(!W){W=!0;B(V,function(a){var i=a.map,j=i.id;if(a.enabled&&(i.isDefine||l.push(a),!a.error))if(!a.inited&&c)e(j)?g=b=!0:(f.push(j),d(j));else if(!a.inited&&(a.fetched&&i.isDefine)&&(g=!0,!i.prefix))return h=!1});if(c&&f.length)return a=C("timeout","Load timeout for modules: "+f,null,
f),a.contextName=i.contextName,w(a);h&&v(l,function(a){F(a,{},{})});if((!c||b)&&g)if((z||ea)&&!X)X=setTimeout(function(){X=0;D()},50);W=!1}}function E(a){t(r,a[0])||s(p(a[0],null,!0)).init(a[1],a[2])}function I(a){var a=a.currentTarget||a.srcElement,b=i.onScriptLoad;a.detachEvent&&!Y?a.detachEvent("onreadystatechange",b):a.removeEventListener("load",b,!1);b=i.onScriptError;(!a.detachEvent||Y)&&a.removeEventListener("error",b,!1);return{node:a,id:a&&a.getAttribute("data-requiremodule")}}function J(){var a;
for(x();A.length;){a=A.shift();if(null===a[0])return w(C("mismatch","Mismatched anonymous define() module: "+a[a.length-1]));E(a)}}var W,Z,i,L,X,j={waitSeconds:7,baseUrl:"./",paths:{},bundles:{},pkgs:{},shim:{},config:{}},h={},V={},$={},A=[],r={},S={},aa={},K=1,O=1;L={require:function(a){return a.require?a.require:a.require=i.makeRequire(a.map)},exports:function(a){a.usingExports=!0;if(a.map.isDefine)return a.exports?r[a.map.id]=a.exports:a.exports=r[a.map.id]={}},module:function(a){return a.module?
a.module:a.module={id:a.map.id,uri:a.map.url,config:function(){return m(j.config,a.map.id)||{}},exports:a.exports||(a.exports={})}}};Z=function(a){this.events=m($,a.id)||{};this.map=a;this.shim=m(j.shim,a.id);this.depExports=[];this.depMaps=[];this.depMatched=[];this.pluginMaps={};this.depCount=0};Z.prototype={init:function(a,b,c,f){f=f||{};if(!this.inited){this.factory=b;if(c)this.on("error",c);else this.events.error&&(c=u(this,function(a){this.emit("error",a)}));this.depMaps=a&&a.slice(0);this.errback=
c;this.inited=!0;this.ignore=f.ignore;f.enabled||this.enabled?this.enable():this.check()}},defineDep:function(a,b){this.depMatched[a]||(this.depMatched[a]=!0,this.depCount-=1,this.depExports[a]=b)},fetch:function(){if(!this.fetched){this.fetched=!0;i.startTime=(new Date).getTime();var a=this.map;if(this.shim)i.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],u(this,function(){return a.prefix?this.callPlugin():this.load()}));else return a.prefix?this.callPlugin():this.load()}},load:function(){var a=
this.map.url;S[a]||(S[a]=!0,i.load(this.map.id,a))},check:function(){if(this.enabled&&!this.enabling){var a,b,c=this.map.id;b=this.depExports;var f=this.exports,l=this.factory;if(this.inited)if(this.error)this.emit("error",this.error);else{if(!this.defining){this.defining=!0;if(1>this.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||g.onError!==ca)try{f=i.execCb(c,l,b,f)}catch(d){a=d}else f=i.execCb(c,l,b,f);this.map.isDefine&&void 0===f&&((b=this.module)?f=b.exports:this.usingExports&&
(f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(r[c]=f,g.onResourceLoad))g.onResourceLoad(i,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete=!0)}}else this.fetch()}},callPlugin:function(){var a=
this.map,b=a.id,d=p(a.prefix);this.depMaps.push(d);q(d,"defined",u(this,function(f){var l,d;d=m(aa,this.map.id);var e=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,n=i.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(e=f.normalize(e,function(a){return c(a,P,!0)})||""),f=p(a.prefix+"!"+e,this.map.parentMap),q(f,"defined",u(this,function(a){this.init([],function(){return a},null,{enabled:!0,ignore:!0})})),d=m(h,f.id)){this.depMaps.push(f);
if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=i.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];B(h,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,e=p(d),P=M;c&&(f=c);P&&(M=!1);s(e);t(j.config,b)&&(j.config[d]=j.config[b]);try{g.exec(f)}catch(h){return w(C("fromtexteval",
"fromText eval for "+b+" failed: "+h,h,[b]))}P&&(M=!0);this.depMaps.push(e);i.completeLoad(d);n([d],l)}),f.load(a.name,n,l,j))}));i.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=p(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c=m(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;q(a,"defined",u(this,function(a){this.defineDep(b,
a);this.check()}));this.errback?q(a,"error",u(this,this.errback)):this.events.error&&q(a,"error",u(this,function(a){this.emit("error",a)}))}c=a.id;f=h[c];!t(L,c)&&(f&&!f.enabled)&&i.enable(a,this)}));B(this.pluginMaps,u(this,function(a){var b=m(h,a.id);b&&!b.enabled&&i.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]=[]);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};i={config:j,contextName:b,
registry:h,defined:r,urlFetched:S,defQueue:A,Module:Z,makeModuleMap:p,nextTick:g.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=j.shim,c={paths:!0,bundles:!0,config:!0,map:!0};B(a,function(a,b){c[b]?(j[b]||(j[b]={}),U(j[b],a,!0,!0)):j[b]=a});a.bundles&&B(a.bundles,function(a,b){v(a,function(a){a!==b&&(aa[a]=b)})});a.shim&&(B(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=i.makeShimExports(a);
b[c]=a}),j.shim=b);a.packages&&v(a.packages,function(a){var b,a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(j.paths[b]=a.location);j.pkgs[b]=a.name+"/"+(a.main||"main").replace(ia,"").replace(Q,"")});B(h,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=p(b))});if(a.deps||a.callback)i.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b;a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,e){function j(c,d,m){var n,
q;e.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild=!0);if("string"===typeof c){if(G(d))return w(C("requireargs","Invalid require call"),m);if(a&&t(L,c))return L[c](h[a.id]);if(g.get)return g.get(i,c,a,j);n=p(c,a,!1,!0);n=n.id;return!t(r,n)?w(C("notloaded",'Module name "'+n+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):r[n]}J();i.nextTick(function(){J();q=s(p(null,a));q.skipMap=e.skipMap;q.init(c,d,m,{enabled:!0});D()});return j}e=e||{};U(j,{isBrowser:z,toUrl:function(b){var d,
e=b.lastIndexOf("."),k=b.split("/")[0];if(-1!==e&&(!("."===k||".."===k)||1<e))d=b.substring(e,b.length),b=b.substring(0,e);return i.nameToUrl(c(b,a&&a.id,!0),d,!0)},defined:function(b){return t(r,p(b,a,!1,!0).id)},specified:function(b){b=p(b,a,!1,!0).id;return t(r,b)||t(h,b)}});a||(j.undef=function(b){x();var c=p(b,a,!0),e=m(h,b);d(b);delete r[b];delete S[c.url];delete $[b];T(A,function(a,c){a[0]===b&&A.splice(c,1)});e&&(e.events.defined&&($[b]=e.events),y(b))});return j},enable:function(a){m(h,a.id)&&
s(a).enable()},completeLoad:function(a){var b,c,d=m(j.shim,a)||{},g=d.exports;for(x();A.length;){c=A.shift();if(null===c[0]){c[0]=a;if(b)break;b=!0}else c[0]===a&&(b=!0);E(c)}c=m(h,a);if(!b&&!t(r,a)&&c&&!c.inited){if(j.enforceDefine&&(!g||!da(g)))return e(a)?void 0:w(C("nodefine","No define call for "+a,null,[a]));E([a,d.deps||[],d.exportsFn])}D()},nameToUrl:function(a,b,c){var d,e,h;(d=m(j.pkgs,a))&&(a=d);if(d=m(aa,a))return i.nameToUrl(d,b,c);if(g.jsExtRegExp.test(a))d=a+(b||"");else{d=j.paths;
a=a.split("/");for(e=a.length;0<e;e-=1)if(h=a.slice(0,e).join("/"),h=m(d,h)){H(h)&&(h=h[0]);a.splice(0,e,h);break}d=a.join("/");d+=b||(/^data\:|\?/.test(d)||c?"":".js");d=("/"===d.charAt(0)||d.match(/^[\w\+\.\-]+:/)?"":j.baseUrl)+d}return j.urlArgs?d+((-1===d.indexOf("?")?"?":"&")+j.urlArgs):d},load:function(a,b){g.load(i,a,b)},execCb:function(a,b,c,d){return b.apply(d,c)},onScriptLoad:function(a){if("load"===a.type||ja.test((a.currentTarget||a.srcElement).readyState))N=null,a=I(a),i.completeLoad(a.id)},
onScriptError:function(a){var b=I(a);if(!e(b.id))return w(C("scripterror","Script error for: "+b.id,a,[b.id]))}};i.require=i.makeRequire();return i}var g,x,y,D,I,E,N,J,s,O,ka=/(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,la=/[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,Q=/\.js$/,ia=/^\.\//;x=Object.prototype;var K=x.toString,fa=x.hasOwnProperty,ha=Array.prototype.splice,z=!!("undefined"!==typeof window&&"undefined"!==typeof navigator&&window.document),ea=!z&&"undefined"!==typeof importScripts,ja=
z&&"PLAYSTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/,Y="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),F={},q={},R=[],M=!1;if("undefined"===typeof define){if("undefined"!==typeof requirejs){if(G(requirejs))return;q=requirejs;requirejs=void 0}"undefined"!==typeof require&&!G(require)&&(q=require,require=void 0);g=requirejs=function(b,c,d,e){var n,p="_";!H(b)&&"string"!==typeof b&&(n=b,H(c)?(b=c,c=d,d=e):b=[]);n&&n.context&&(p=n.context);(e=m(F,p))||(e=F[p]=g.s.newContext(p));
n&&e.configure(n);return e.require(b,c,d)};g.config=function(b){return g(b)};g.nextTick="undefined"!==typeof setTimeout?function(b){setTimeout(b,4)}:function(b){b()};require||(require=g);g.version="2.1.16";g.jsExtRegExp=/^\/|:|\?|\.js$/;g.isBrowser=z;x=g.s={contexts:F,newContext:ga};g({});v(["toUrl","undef","defined","specified"],function(b){g[b]=function(){var c=F._;return c.require[b].apply(c,arguments)}});if(z&&(y=x.head=document.getElementsByTagName("head")[0],D=document.getElementsByTagName("base")[0]))y=
x.head=D.parentNode;g.onError=ca;g.createNode=function(b){var c=b.xhtml?document.createElementNS("http://www.w3.org/1999/xhtml","html:script"):document.createElement("script");c.type=b.scriptType||"text/javascript";c.charset="utf-8";c.async=!0;return c};g.load=function(b,c,d){var e=b&&b.config||{};if(z)return e=g.createNode(e,c,d),e.setAttribute("data-requirecontext",b.contextName),e.setAttribute("data-requiremodule",c),e.attachEvent&&!(e.attachEvent.toString&&0>e.attachEvent.toString().indexOf("[native code"))&&
!Y?(M=!0,e.attachEvent("onreadystatechange",b.onScriptLoad)):(e.addEventListener("load",b.onScriptLoad,!1),e.addEventListener("error",b.onScriptError,!1)),e.src=d,J=e,D?y.insertBefore(e,D):y.appendChild(e),J=null,e;if(ea)try{importScripts(d),b.completeLoad(c)}catch(m){b.onError(C("importscripts","importScripts failed for "+c+" at "+d,m,[c]))}};z&&!q.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return s=I,q.baseUrl||(E=s.split("/"),
s=E.pop(),O=E.length?E.join("/")+"/":"./",q.baseUrl=O),s=s.replace(Q,""),g.jsExtRegExp.test(s)&&(s=I),q.deps=q.deps?q.deps.concat(s):[s],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ka,"").replace(la,function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),function(b){if("interactive"===
b.readyState)return N=b}),e=N;e&&(b||(b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}(g?g.defQueue:R).push([b,c,d])};define.amd={jQuery:!0};g.exec=function(b){return eval(b)};g(q)}})(this);

4
content/search/search-results-template.mustache

@ -1,4 +0,0 @@ @@ -1,4 +0,0 @@
<article>
<h3><a href="{{location}}">{{title}}</a></h3>
<p>{{summary}}</p>
</article>

92
content/search/search.js

@ -1,92 +0,0 @@ @@ -1,92 +0,0 @@
require.config({
baseUrl: base_url + "/search/"
});
require([
'mustache.min',
'lunr.min',
'text!search-results-template.mustache',
'text!search_index.json',
], function (Mustache, lunr, results_template, data) {
"use strict";
function getSearchTerm()
{
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length; i++)
{
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == 'q')
{
return decodeURIComponent(sParameterName[1].replace(/\+/g, '%20'));
}
}
}
var index = lunr(function () {
this.field('title', {boost: 10});
this.field('text');
this.ref('location');
});
data = JSON.parse(data);
var documents = {};
for (var i=0; i < data.docs.length; i++){
var doc = data.docs[i];
doc.location = base_url + doc.location;
index.add(doc);
documents[doc.location] = doc;
}
var search = function(){
var query = document.getElementById('mkdocs-search-query').value;
var search_results = document.getElementById("mkdocs-search-results");
while (search_results.firstChild) {
search_results.removeChild(search_results.firstChild);
}
if(query === ''){
return;
}
var results = index.search(query);
if (results.length > 0){
for (var i=0; i < results.length; i++){
var result = results[i];
doc = documents[result.ref];
doc.base_url = base_url;
doc.summary = doc.text.substring(0, 200);
var html = Mustache.to_html(results_template, doc);
search_results.insertAdjacentHTML('beforeend', html);
}
} else {
search_results.insertAdjacentHTML('beforeend', "<p>No results found</p>");
}
if(jQuery){
/*
* We currently only automatically hide bootstrap models. This
* requires jQuery to work.
*/
jQuery('#mkdocs_search_modal a').click(function(){
jQuery('#mkdocs_search_modal').modal('hide');
});
}
};
var search_input = document.getElementById('mkdocs-search-query');
var term = getSearchTerm();
if (term){
search_input.value = term;
search();
}
if (search_input){search_input.addEventListener("keyup", search);}
});

34
content/search/search_index.json

@ -1,34 +0,0 @@ @@ -1,34 +0,0 @@
{
"docs": [
{
"location": "/",
"text": "Secret Index\n\n\nThis is an index of secret pages. Note that this page is not protected.\n\n\nYou will be asked to authenticate when you click the links below.\n\n\nIf your username has an \neven number of vowels\n, you are a member \nof the \nSecret Society of the Fish Slappers\n.\nClick the link to log in and proceed, or else be attacked by \nthe Github-Heroku attack rabbits.\n\n\nIf your username has an \nodd number of vowels\n, you are a member \nof the \nSecret Ministerial Department for Theoretical Silly Walk Studies\n.\nClick the link to log in and proceed, or else be attacked by \nthe Github-Heroku attack rabbits.",
"title": "Index"
},
{
"location": "/#secret-index",
"text": "This is an index of secret pages. Note that this page is not protected. You will be asked to authenticate when you click the links below. If your username has an even number of vowels , you are a member \nof the Secret Society of the Fish Slappers .\nClick the link to log in and proceed, or else be attacked by \nthe Github-Heroku attack rabbits. If your username has an odd number of vowels , you are a member \nof the Secret Ministerial Department for Theoretical Silly Walk Studies .\nClick the link to log in and proceed, or else be attacked by \nthe Github-Heroku attack rabbits.",
"title": "Secret Index"
},
{
"location": "/fishslap/",
"text": "The Secret Society of the Fish Slapping Dance\n\n\nWelcome, and congratulations on having an even number of vowels in your Github handle. \n\n\nAnd now for something completely expected:\n\n\nFish-Slapping Dance",
"title": "Fish Slapping"
},
{
"location": "/fishslap/#the-secret-society-of-the-fish-slapping-dance",
"text": "Welcome, and congratulations on having an even number of vowels in your Github handle. And now for something completely expected: Fish-Slapping Dance",
"title": "The Secret Society of the Fish Slapping Dance"
},
{
"location": "/sillywalk/",
"text": "The Secret Ministry of Silly Walks\n\n\nWelcome, and congratulations on having an odd number of vowels in your Github handle. \n\n\nAnd now for something completely expected:\n\n\nMinistry of Sily Walks",
"title": "Silly Walks"
},
{
"location": "/sillywalk/#the-secret-ministry-of-silly-walks",
"text": "Welcome, and congratulations on having an odd number of vowels in your Github handle. And now for something completely expected: Ministry of Sily Walks",
"title": "The Secret Ministry of Silly Walks"
}
]
}

390
content/search/text.js

@ -1,390 +0,0 @@ @@ -1,390 +0,0 @@
/**
* @license RequireJS text 2.0.12 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/requirejs/text for details
*/
/*jslint regexp: true */
/*global require, XMLHttpRequest, ActiveXObject,
define, window, process, Packages,
java, location, Components, FileUtils */
define(['module'], function (module) {
'use strict';
var text, fs, Cc, Ci, xpcIsWindows,
progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im,
bodyRegExp = /<body[^>]*>\s*([\s\S]+)\s*<\/body>/im,
hasLocation = typeof location !== 'undefined' && location.href,
defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''),
defaultHostName = hasLocation && location.hostname,
defaultPort = hasLocation && (location.port || undefined),
buildMap = {},
masterConfig = (module.config && module.config()) || {};
text = {
version: '2.0.12',
strip: function (content) {
//Strips <?xml ...?> declarations so that external SVG and XML
//documents can be added to a document without worry. Also, if the string
//is an HTML document, only the part inside the body tag is returned.
if (content) {
content = content.replace(xmlRegExp, "");
var matches = content.match(bodyRegExp);
if (matches) {
content = matches[1];
}
} else {
content = "";
}
return content;
},
jsEscape: function (content) {
return content.replace(/(['\\])/g, '\\$1')
.replace(/[\f]/g, "\\f")
.replace(/[\b]/g, "\\b")
.replace(/[\n]/g, "\\n")
.replace(/[\t]/g, "\\t")
.replace(/[\r]/g, "\\r")
.replace(/[\u2028]/g, "\\u2028")
.replace(/[\u2029]/g, "\\u2029");
},
createXhr: masterConfig.createXhr || function () {
//Would love to dump the ActiveX crap in here. Need IE 6 to die first.
var xhr, i, progId;
if (typeof XMLHttpRequest !== "undefined") {
return new XMLHttpRequest();
} else if (typeof ActiveXObject !== "undefined") {
for (i = 0; i < 3; i += 1) {
progId = progIds[i];
try {
xhr = new ActiveXObject(progId);
} catch (e) {}
if (xhr) {
progIds = [progId]; // so faster next time
break;
}
}
}
return xhr;
},
/**
* Parses a resource name into its component parts. Resource names
* look like: module/name.ext!strip, where the !strip part is
* optional.
* @param {String} name the resource name
* @returns {Object} with properties "moduleName", "ext" and "strip"
* where strip is a boolean.
*/
parseName: function (name) {
var modName, ext, temp,
strip = false,
index = name.indexOf("."),
isRelative = name.indexOf('./') === 0 ||
name.indexOf('../') === 0;
if (index !== -1 && (!isRelative || index > 1)) {
modName = name.substring(0, index);
ext = name.substring(index + 1, name.length);
} else {
modName = name;
}
temp = ext || modName;
index = temp.indexOf("!");
if (index !== -1) {
//Pull off the strip arg.
strip = temp.substring(index + 1) === "strip";
temp = temp.substring(0, index);
if (ext) {
ext = temp;
} else {
modName = temp;
}
}
return {
moduleName: modName,
ext: ext,
strip: strip
};
},
xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/,
/**
* Is an URL on another domain. Only works for browser use, returns
* false in non-browser environments. Only used to know if an
* optimized .js version of a text resource should be loaded
* instead.
* @param {String} url
* @returns Boolean
*/
useXhr: function (url, protocol, hostname, port) {
var uProtocol, uHostName, uPort,
match = text.xdRegExp.exec(url);
if (!match) {
return true;
}
uProtocol = match[2];
uHostName = match[3];
uHostName = uHostName.split(':');
uPort = uHostName[1];
uHostName = uHostName[0];
return (!uProtocol || uProtocol === protocol) &&
(!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) &&
((!uPort && !uHostName) || uPort === port);
},
finishLoad: function (name, strip, content, onLoad) {
content = strip ? text.strip(content) : content;
if (masterConfig.isBuild) {
buildMap[name] = content;
}
onLoad(content);
},
load: function (name, req, onLoad, config) {
//Name has format: some.module.filext!strip
//The strip part is optional.
//if strip is present, then that means only get the string contents
//inside a body tag in an HTML string. For XML/SVG content it means
//removing the <?xml ...?> declarations so the content can be inserted
//into the current doc without problems.
// Do not bother with the work if a build and text will
// not be inlined.
if (config && config.isBuild && !config.inlineText) {
onLoad();
return;
}
masterConfig.isBuild = config && config.isBuild;
var parsed = text.parseName(name),
nonStripName = parsed.moduleName +
(parsed.ext ? '.' + parsed.ext : ''),
url = req.toUrl(nonStripName),
useXhr = (masterConfig.useXhr) ||
text.useXhr;
// Do not load if it is an empty: url
if (url.indexOf('empty:') === 0) {
onLoad();
return;
}
//Load the text. Use XHR if possible and in a browser.
if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) {
text.get(url, function (content) {
text.finishLoad(name, parsed.strip, content, onLoad);
}, function (err) {
if (onLoad.error) {
onLoad.error(err);
}
});
} else {
//Need to fetch the resource across domains. Assume
//the resource has been optimized into a JS module. Fetch
//by the module name + extension, but do not include the
//!strip part to avoid file system issues.
req([nonStripName], function (content) {
text.finishLoad(parsed.moduleName + '.' + parsed.ext,
parsed.strip, content, onLoad);
});
}
},
write: function (pluginName, moduleName, write, config) {
if (buildMap.hasOwnProperty(moduleName)) {
var content = text.jsEscape(buildMap[moduleName]);
write.asModule(pluginName + "!" + moduleName,
"define(function () { return '" +
content +
"';});\n");
}
},
writeFile: function (pluginName, moduleName, req, write, config) {
var parsed = text.parseName(moduleName),
extPart = parsed.ext ? '.' + parsed.ext : '',
nonStripName = parsed.moduleName + extPart,
//Use a '.js' file name so that it indicates it is a
//script that can be loaded across domains.
fileName = req.toUrl(parsed.moduleName + extPart) + '.js';
//Leverage own load() method to load plugin value, but only
//write out values that do not have the strip argument,
//to avoid any potential issues with ! in file names.
text.load(nonStripName, req, function (value) {
//Use own write() method to construct full module value.
//But need to create shell that translates writeFile's
//write() to the right interface.
var textWrite = function (contents) {
return write(fileName, contents);
};
textWrite.asModule = function (moduleName, contents) {
return write.asModule(moduleName, fileName, contents);
};
text.write(pluginName, nonStripName, textWrite, config);
}, config);
}
};
if (masterConfig.env === 'node' || (!masterConfig.env &&
typeof process !== "undefined" &&
process.versions &&
!!process.versions.node &&
!process.versions['node-webkit'])) {
//Using special require.nodeRequire, something added by r.js.
fs = require.nodeRequire('fs');
text.get = function (url, callback, errback) {
try {
var file = fs.readFileSync(url, 'utf8');
//Remove BOM (Byte Mark Order) from utf8 files if it is there.
if (file.indexOf('\uFEFF') === 0) {
file = file.substring(1);
}
callback(file);
} catch (e) {
if (errback) {
errback(e);
}
}
};
} else if (masterConfig.env === 'xhr' || (!masterConfig.env &&
text.createXhr())) {
text.get = function (url, callback, errback, headers) {
var xhr = text.createXhr(), header;
xhr.open('GET', url, true);
//Allow plugins direct access to xhr headers
if (headers) {
for (header in headers) {
if (headers.hasOwnProperty(header)) {
xhr.setRequestHeader(header.toLowerCase(), headers[header]);
}
}
}
//Allow overrides specified in config
if (masterConfig.onXhr) {
masterConfig.onXhr(xhr, url);
}
xhr.onreadystatechange = function (evt) {
var status, err;
//Do not explicitly handle errors, those should be
//visible via console output in the browser.
if (xhr.readyState === 4) {
status = xhr.status || 0;
if (status > 399 && status < 600) {
//An http 4xx or 5xx error. Signal an error.
err = new Error(url + ' HTTP status: ' + status);
err.xhr = xhr;
if (errback) {
errback(err);
}
} else {
callback(xhr.responseText);
}
if (masterConfig.onXhrComplete) {
masterConfig.onXhrComplete(xhr, url);
}
}
};
xhr.send(null);
};
} else if (masterConfig.env === 'rhino' || (!masterConfig.env &&
typeof Packages !== 'undefined' && typeof java !== 'undefined')) {
//Why Java, why is this so awkward?
text.get = function (url, callback) {
var stringBuffer, line,
encoding = "utf-8",
file = new java.io.File(url),
lineSeparator = java.lang.System.getProperty("line.separator"),
input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)),
content = '';
try {
stringBuffer = new java.lang.StringBuffer();
line = input.readLine();
// Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324
// http://www.unicode.org/faq/utf_bom.html
// Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK:
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058
if (line && line.length() && line.charAt(0) === 0xfeff) {
// Eat the BOM, since we've already found the encoding on this file,
// and we plan to concatenating this buffer with others; the BOM should
// only appear at the top of a file.
line = line.substring(1);
}
if (line !== null) {
stringBuffer.append(line);
}
while ((line = input.readLine()) !== null) {
stringBuffer.append(lineSeparator);
stringBuffer.append(line);
}
//Make sure we return a JavaScript string and not a Java string.
content = String(stringBuffer.toString()); //String
} finally {
input.close();
}
callback(content);
};
} else if (masterConfig.env === 'xpconnect' || (!masterConfig.env &&
typeof Components !== 'undefined' && Components.classes &&
Components.interfaces)) {
//Avert your gaze!
Cc = Components.classes;
Ci = Components.interfaces;
Components.utils['import']('resource://gre/modules/FileUtils.jsm');
xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc);
text.get = function (url, callback) {
var inStream, convertStream, fileObj,
readData = {};
if (xpcIsWindows) {
url = url.replace(/\//g, '\\');
}
fileObj = new FileUtils.File(url);
//XPCOM, you so crazy
try {
inStream = Cc['@mozilla.org/network/file-input-stream;1']
.createInstance(Ci.nsIFileInputStream);
inStream.init(fileObj, 1, 0, false);
convertStream = Cc['@mozilla.org/intl/converter-input-stream;1']
.createInstance(Ci.nsIConverterInputStream);
convertStream.init(inStream, "utf-8", inStream.available(),
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
convertStream.readString(inStream.available(), readData);
convertStream.close();
inStream.close();
callback(readData.value);
} catch (e) {
throw new Error((fileObj && fileObj.path || '') + ': ' + e);
}
};
}
return text;
});

387
content/sillywalk/index.html

@ -1,387 +0,0 @@ @@ -1,387 +0,0 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<link rel="canonical" href="https://github-heroku-attack-rabbits.herokuapp.com/sillywalk/">
<meta name="lang:clipboard.copy" content="Copy to clipboard">
<meta name="lang:clipboard.copied" content="Copied to clipboard">
<meta name="lang:search.language" content="en">
<meta name="lang:search.pipeline.stopwords" content="True">
<meta name="lang:search.pipeline.trimmer" content="True">
<meta name="lang:search.result.none" content="No matching documents">
<meta name="lang:search.result.one" content="1 matching document">
<meta name="lang:search.result.other" content="# matching documents">
<meta name="lang:search.tokenizer" content="[\s\-]+">
<link rel="shortcut icon" href="../">
<meta name="generator" content="mkdocs-0.17.3, mkdocs-material-2.7.2">
<title>Silly Walks - This Is The Secret Site</title>
<link rel="stylesheet" href="../assets/stylesheets/application.8d40d89b.css">
<link rel="stylesheet" href="../assets/stylesheets/application-palette.6079476c.css">
<script src="../assets/javascripts/modernizr.1aa3b519.js"></script>
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Questrial:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Questrial","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<link rel="stylesheet" href="../css/custom.css">
</head>
<body dir="ltr" data-md-color-primary="teal" data-md-color-accent="teal">
<svg class="md-svg">
<defs>
<svg xmlns="http://www.w3.org/2000/svg" width="416" height="448"
viewBox="0 0 416 448" id="github">
<path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19-18.125
8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19 18.125-8.5
18.125 8.5 10.75 19 3.125 20.5zM320 304q0 10-3.125 20.5t-10.75
19-18.125 8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19
18.125-8.5 18.125 8.5 10.75 19 3.125 20.5zM360
304q0-30-17.25-51t-46.75-21q-10.25 0-48.75 5.25-17.75 2.75-39.25
2.75t-39.25-2.75q-38-5.25-48.75-5.25-29.5 0-46.75 21t-17.25 51q0 22 8
38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0
37.25-1.75t35-7.375 30.5-15 20.25-25.75 8-38.375zM416 260q0 51.75-15.25
82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5-41.75
1.125q-19.5 0-35.5-0.75t-36.875-3.125-38.125-7.5-34.25-12.875-30.25-20.25-21.5-28.75q-15.5-30.75-15.5-82.75
0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25
30.875q36.75-8.75 77.25-8.75 37 0 70 8 26.25-20.5
46.75-30.25t47.25-9.75q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34
99.5z" />
</svg>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="search" autocomplete="off">
<label class="md-overlay" data-md-component="overlay" for="drawer"></label>
<a href="#the-secret-ministry-of-silly-walks" tabindex="1" class="md-skip">
Skip to content
</a>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="https://github-heroku-attack-rabbits.herokuapp.com" title="This Is The Secret Site" class="md-header-nav__button md-logo">
<img src="../img/bunny.png" width="24" height="24">
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
<span class="md-header-nav__topic">
This Is The Secret Site
</span>
<span class="md-header-nav__topic">
Silly Walks
</span>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query" data-md-state="active">
<label class="md-icon md-search__icon" for="search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset" tabindex="-1">
&#xE5CD;
</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result">
<div class="md-search-result__meta">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
<a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
charlesreid1/github-heroku-attack-rabbits
</div>
</a>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="drawer">
<span class="md-nav__button md-logo">
<img src="../img/bunny.png" width="48" height="48">
</span>
This Is The Secret Site
</label>
<div class="md-nav__source">
<a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
charlesreid1/github-heroku-attack-rabbits
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." title="Index" class="md-nav__link">
Index
</a>
</li>
<li class="md-nav__item">
<a href="../fishslap/" title="Fish Slapping" class="md-nav__link">
Fish Slapping
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="toc">
<a href="./" title="Silly Walks" class="md-nav__link md-nav__link--active">
Silly Walks
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<h1 id="the-secret-ministry-of-silly-walks">The Secret Ministry of Silly Walks</h1>
<p>Welcome, and congratulations on having an odd number of vowels in your Github handle. </p>
<p>And now for something completely expected:</p>
<p><a href="https://www.youtube.com/watch?v=IqhlQfXUk7w">Ministry of Sily Walks</a></p>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../fishslap/" title="Fish Slapping" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
Fish Slapping
</span>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
<div class="md-footer-copyright__highlight">
Copyright &copy; 2018 Charles Reid, released under the <a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs/LICENSE">WTFPL</a>.<br /><br />Many Bothans died to bring us this documentation.<br /><br />
</div>
powered by
<a href="http://www.mkdocs.org">MkDocs</a>
and
<a href="https://squidfunk.github.io/mkdocs-material/">
Material for MkDocs</a>
</div>
</div>
</div>
</footer>
</div>
<script src="../assets/javascripts/application.0cf9b500.js"></script>
<script>app.initialize({version:"0.17.3",url:{base:".."}})</script>
<script src="../search/require.js"></script>
<script src="../search/search.js"></script>
</body>
</html>

28
content/sitemap.xml

@ -1,28 +0,0 @@ @@ -1,28 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://github-heroku-attack-rabbits.herokuapp.com/</loc>
<lastmod>2018-05-25</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://github-heroku-attack-rabbits.herokuapp.com/fishslap/</loc>
<lastmod>2018-05-25</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>https://github-heroku-attack-rabbits.herokuapp.com/sillywalk/</loc>
<lastmod>2018-05-25</lastmod>
<changefreq>daily</changefreq>
</url>
</urlset>

10
docs/css/custom.css

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
body {
background-color: #efefef;
}
div.body {
background-color: #efefef;
}
.md-typeset pre {
background-color: #ccc;
}

33
docs/custom_domains.md

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
# Custom Domains
If you want to use a custom domain:
* Set up custom domain using Heroku command line interface
* This will set up a DNS subdomain specifically for your app
* Point your DNS records to the Heroku DNS subdomain
This also introduces complications with HTTP vs HTTPS:
OAuth must happen over HTTPS (required by protocol).
But you the user can control your domain and create
SSL certificates for it, but Heroku is hosting the app.
You have two options: the free option, and the pay option.
**The pay option:** For $7/mo you can upgrade to hobby nodes,
which allows you to give Heroku permission to create an SSL
certificate for your domain. This is the easiest solution
and requires zero setup, zero certificate management.
**The free option:** You can have your domain (HTTP only)
forward to Heroku (HTTPS can't be forwarded - that's ***key***).
When you hit the Heroku domain, it will log the user in to Github.
When the user logs in successfully, Github will redirect them to
the callback URL. This callback URL ***MUST*** be HTTPS, so it cannot
redirect back to your (HTTP-only) custom domain.
That means the userr will, after authenticating with Github,
always be redirected to `https://my-cool-app.herokuapp.com`
and never `http://my-cool-custom-domain-that-cannot-be-used-as-a-callback-because-it-is-https-only.com`.
The paid option is much, much simpler in the end
and will save you $7/mo in setup time alone.

174
docs/flask.md

@ -0,0 +1,174 @@ @@ -0,0 +1,174 @@
# Create a Flask App using Flask-Dance
This is the heart of the method.
The best thing to do here is just to walk you through the script.
Import statements:
```
import os, json
from os.path import join, isfile, isdir
from werkzeug.contrib.fixers import ProxyFix
from flask import Flask, redirect, url_for, send_from_directory
from flask_dance.contrib.github import make_github_blueprint, github
```
Note that flask-dance adds an OAuth login/callback route
to your Flask app by creating a `/login` blueprint,
meaning all the OAuth stuff is just magically available
via `/login`.
Set paths for static content:
```
PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))
STATIC_PATH = 'content'
```
Create and configure app. This requires confidential information
for the Github application you created, specifically the client
ID and client secret. These are at the very top of the page when
you visit your app's settings page.
To find this, after you log in, click your profile photo in the
upper right > Settings > Developer Settings > OAuth Apps > click the
name for your OAuth app.
```
app = Flask(__name__)
# this worked locally, but not on heroku
app.wsgi_app = ProxyFix(app.wsgi_app)
app.secret_key = os.environ.get("FLASK_SECRET_KEY", "9502861d41e8729c5cae3225920b1b46")
app.config["RESULT_STATIC_PATH"] = STATIC_PATH #os.path.join(PROJECT_ROOT,STATIC_PATH)
app.config["GITHUB_OAUTH_CLIENT_ID"] = os.environ.get("GITHUB_OAUTH_CLIENT_ID")
app.config["GITHUB_OAUTH_CLIENT_SECRET"] = os.environ.get("GITHUB_OAUTH_CLIENT_SECRET")
```
Now the magic happens: we use flask-dance to create a blueprint
that has methods and settings all ready to go for us to do the
OAuth dance.
`make_github_blueprint()` is part of the contrib module of flask-dance.
There are several similar methods to generate blueprints for
authenticating with other APIs.
```
github_bp = make_github_blueprint(
client_id = os.environ.get('GITHUB_OAUTH_CLIENT_ID'),
client_secret = os.environ.get('GITHUB_OAUTH_CLIENT_SECRET'),
scope='read:org')
app.register_blueprint(github_bp, url_prefix="/login")
contents404 = "<html><body><h1>Status: Error 404 Page Not Found</h1></body></html>"
contents403 = "<html><body><h1>Status: Error 403 Access Denied</h1></body></html>"
contents200 = "<html><body><h1>Status: OK 200</h1></body></html>"
```
Deal with the `/` route first:
* Check if authorized, if not, redirect them to the login URL
(magical URLs taken care of magically by our `make_github_blueprint`
function above)
* If user is authorized (i.e., if they have gone through the OAuth
process and given their passsword to Github and been redirected
to your app with an OAuth token), then the next step is to
find out some information about them.
* Use the `github` object to call the Github API directly.
* Decide what to do from there.
```
@app.route('/')
def index():
if not github.authorized:
return redirect(url_for("github.login"))
resp = github.get("/user/orgs")
if resp.ok:
all_orgs = resp.json()
for org in all_orgs:
if org['login']=='rainbow-mind-machine':
```
The next line is important to how the server works:
if all of the criteria above have been met, we return
a static file:
```
return send_from_directory(STATIC_PATH, 'index.html')
```
This is normally "bad practice," and numerous type A people
on the internet will tell you Flask should not be used for
serving static files, and that you should use nginx etc.,
but these fail to igonore the following:
* Heroku does not let you run or configure nginx
* For crying out loud this example is about attack rabbits
stop taking everything so seriously
Now that we've got that out of the way...
Here's how we serve up static files. This is a total hack,
but it works. It takes any arbitrary path supplied by the
user, and attempts to find a corresponding file to serve up
on disk.
If the user passes a file, then that file is served up.
If the user passes a directory, then `index.html` is served up.
If the user asks for a non-existent file, a 404 error is shown.
If the user is not allowed to view the content, they will face
the bowel-emptying terrors of the 403 error.
```
@app.route('/<path:path>')
def catch_all(path):
if not github.authorized:
return redirect(url_for("github.login"))
username = github.get("/user").json()['login']
rsp = app.config["RESULT_STATIC_PATH"]
resp = github.get("/user/orgs")
if resp.ok:
all_orgs = resp.json()
for org in all_orgs:
if org['login']=='dcppc':
if(path==''):
return send_from_directory(rsp, 'index.html')
elif(isdir(join(rsp,path))):
return send_from_directory(join(rsp,path),'index.html')
elif(isfile(join(rsp,path))):
return send_from_directory(rsp, path)
else:
return contents404
return contents403
```
Last, set a default 404 handler, and run the app:
```
@app.errorhandler(404)
def oops(e):
return contents404
if __name__ == "__main__":
app.run()
```

25
docs/flask_auth_github.md

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
# Authenticate Users on Github Membership
For the sake of simplicity, will just demonstrate how this
works for a single route, but you can combine different
rules into multiple routes to provide access to different
people on different parts of a site.
Here is the relevant method that serves up `index.html`
if the user is authenticated:
```
@app.route('/')
def index():
if not github.authorized:
return redirect(url_for("github.login"))
resp = github.get("/user")
if resp.ok:
return send_from_directory(STATIC_PATH, 'index.html')
return contents403
```

22
docs/flask_auth_org.md

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
# Authenticate Users on Organization or Team Membership
Here is how we can make access to a given page or route
conditional on membership in an organization (in this
example, membership in the `rainbow-mind-machine` organization
is required for access):
```
@app.route('/')
def index():
if not github.authorized:
return redirect(url_for("github.login"))
resp = github.get("/user/orgs")
if resp.ok:
all_orgs = resp.json()
for org in all_orgs:
if org['login']=='rainbow-mind-machine':
return send_from_directory(STATIC_PATH, 'index.html')
```

27
docs/flask_auth_other.md

@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
# Authenticate Users on Other Criteria
Suppose we wanted to do something silly like restrict
access to a page to users with Github handles that were
between 5 and 7 letters.
Here is the relevant method that serves up `index.html`
if the user's Github handle is 5-7 letters long:
```
@app.route('/')
def index():
if not github.authorized:
return redirect(url_for("github.login"))
resp = github.get("/user")
if resp.ok:
username = resp.json()['login']
if len(username)>=5 and len(username)<=7:
return send_from_directory(STATIC_PATH, 'index.html')
return contents403
```

64
docs/flask_auth_portions.md

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
# Authenticate Different Users on Different Portions of Site
**NOTE: if you are authenticating using membership on a team, you will need the
team id of the team you are interested in authenticating against.**
For this example, let's expand the routes we're looking at
a bit more.
Suppose we have two folders, `team_only` and `org_only`.
The `team_only` folder should only be accessible to your team, `Team Gold`.
The `org_only` folder should only be accessible to your organization, `Colorful Colors`.
The main site (i.e., all other files) should be publicly accessible.
```
@app.route('/')
def index():
return send_from_directory(STATIC_PATH, 'index.html')
@app.route('/team_only/<path:path>')
def team_gold(path):
if not github.authorized:
return redirect(url_for("github.login"))
rsp = app.config["RESULT_STATIC_PATH"]
resp = github.get("/user")
if resp.ok:
username = resp.json()['login']
team_id = 'XXXXX'
resp = github.get("/teams/%s/members/%s"%( team_id, username ))
if resp.code==204:
team_gold_dir = os.path.join(STATIC_PATH, 'team_only')
return send_from_directory(team_gold_dir, 'index.html')
return contents403
@app.route('/org_only/<path:path>')
def team_gold(path):
if not github.authorized:
return redirect(url_for("github.login"))
rsp = app.config["RESULT_STATIC_PATH"]
resp = github.get("/user")
if resp.ok:
my_org = 'XXXXXXXX'
all_orgs = resp.json()
for org in all_orgs:
if org['login']==my_org:
color_org_dir = os.path.join(STATIC_PATH, 'org_only')
return send_from_directory(color_org_dir, 'index.html')
return contents403
```

98
docs/flask_heroku.md

@ -0,0 +1,98 @@ @@ -0,0 +1,98 @@
# Deploying to Heroku
Once we have debugged the Flask app and we are happy with it,
we are ready to deploy it to Heroku.
## Repository Setup
To do this, you should set up your repo as follows:
Clone the repo:
```
$ git clone https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits.git
```
The repo has the following structure:
```
github-heroku-attack-rabbits/
LICENSE
README.md
mkdocs.yml
docs/
index.md
...
mkdocs-material/
...
```
Now, inside the repo, clone the repo again,
but this time clone the `heroku-pages` branch
to the `site/` directory:
```
$ cd github-heroku-attack-rabbits/
$ git clone -b heroku-pages https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits.git site
```
## Heroku Deploy Process
To deploy content to Heroku, we add our Heroku project as a git remote
(see the [heroku](heroku.md) page for how to do that) and then push to
to the master branch of the heroku remote git repo. Changes are pulled
in by Heroku and the app is restarted each time you run `git push`.
We walk through the steps below.
## Heroku Login
From the `site/` directory containing the contents of the `heroku-pages` branch,
that is, containing the Python flask app, log in to Heroku:
```
$ heroku login
```
## Add Heroku Remote to `heroku-pages` Branch
Now have Heroku add the proper git remote address:
```
$ heroku git:remote -a <heroku-app-name>
```
Now you're ready to deploy to Heroku.
## Deploy to Heroku
Double check your app is ready, then deploy:
```
$ git push heroku heroku-pages:master
```
This will push the local branch `heroku-pages` to
the remote branch `master` on the `heroku` remote.
This should begin a pre-commit hook where Heroku
compiles your Python app. You should get the green
light, if you tested your app locally and everything
was good to go.
## Check Your Heroku App
Your Heroku app will be available at
```
https://<heroku-app-name>.herokuapp.com
```
You should see your Python flask app
show up shortly.

42
docs/flask_local.md

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
# Testing Flask App Locally
We set up the Github App to use a callback of `https://localhost:5000/login/github/authorized`
so that we could test the app locally. Now it is time to test the app locally.
The application needs access to your Github app id and token. Those are provided
via the `GITHUB_OAUTH_CLIENT_{ID,SECRET}` environment variables. Set these
when you run the actual python command to run the server:
```
$ GITHUB_OAUTH_CLIENT_ID="xxxxxxx" \
GITHUB_OAUTH_CLIENT_SECRET="xxxxxxx" \
OAUTHLIB_INSECURE_TRANSPORT=true \
python github.py
```
This runs the Flask server on port 5000, where it will wait for a visitor.
The way we have our application written in this example, the main `/` route
will redirect the user to a Github login screen immediately, but you could
also present the user with a friendly welcome page when they go to `/`,
and only redirect them to the Github login prompt when they visit a
URL like `/login` or `/auth`.
Once you run the above command, open the following URL in your browser:
```
http://localhost:5000/
```
**NOTE: Make sure you are logged out of Github and that you clear your cookies
if you are already logged in as one user and wish to authenticate as another.
The login is _very_ persistent so you may need to close and re-open your browser.**
Visiting the address above will result in your being redirected to a Github
login page. Once you login, Github will redirect the user back to the
github-heroku-attack-rabbits application with a token that the application
can use to perform actions on behalf of the user.
## Next Step?
If the app works, the next step is to deploy to Heroku.

57
docs/github.md

@ -0,0 +1,57 @@ @@ -0,0 +1,57 @@
## get started with github
We mentioned on the [heroku](heroku.md) page that heroku
creates a remote git repository to hold the files you
want to host.
To use the contents of a Github repository on Heroku,
just treat it like another git remote, no special setup
is needed.
However, to set up your Github-Heroku attack rabbits to
authenticate a user via Github, and mercilessly attack
all intruders, you must create a Github OAuth App.
### Creating Github OAuth App
Log into Github
Go to Settings
Click "Developer Settings" on the left side
Click "New OAuth App" button in upper right
**What do these settings mean?**
* **Application name** is what will be shown to users when they visit
a page protected by the attack rabbits and are prompted for
their password by Github.
* **Homepage URL/Application description** are for users who want to know more
about your killer attack rabbit Github app
* **Authorization callback URL** is the URL that the users will be sent to
once they authenticate with Github and they are granted an OAuth token.
This is the magic ingredient that allows you to take actions on behalf
of the account logging in.
In this guide we'll cover the case of checking membership in organizations or teams,
but what your attack rabbits end up doing to determine if a user is allowed to
access your secret pages is up to you.
## Values to use
You should set your own values for the **name** and **description** fields.
The **home URL** is not actually necessary - it is simply provided for users to
get more information about your app.
The most important is the **callback URL**, which should be set to:
http://localhost:5000/login/github/authorized
This is for testing locally *only*.
Don't use HTTPS in the callback URL!

67
docs/heroku.md

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
# create heroku app
### Heroku toolbelt
Heroku offers a really nice command line interface tool called
Heroku Toolbelt. It is available through Homebrew and Aptitude:
```
$ brew install heroku # from Mac
$ apt-get install heroku # from Ubuntu
```
It is then available on the command line as `heroku`.
The first thing you should do is authenticate with
your Heroku account by running
```
$ heroku login
```
We will use this command line application for the following tasks:
* Create a git remote to point to the right Heroku git remote location
* Set environment variables (for e.g. secret keys) on the remote Heroku instance
* Get information (logs, status, etc.) about your Heroku app
We will cover these commands as they come up.
### Create heroku app
Start by creating a heroku app.
* Each heroku app must have a unique name
* Each heroku app creates a remote git repo
* Master branch is what Heroku deploys publicly on herokuapps.com
* You will also need heroku CLI to link your github repo to your heroku app
### Where heroku app lives
Suppose you are creating an app called `my-cool-app`
on heroku. Then your application will be hosted by
Heroku and will be available at the URL:
```
https://my-cool-app.herokuapp.com
```
### How heroku apps works
If you have used Github Pages before, Heroku uses a similar
model (live hosting one particular branch of a git repository).
However, Heroku is different because you can run dynamic scripts
using Python, Ruby, PHP, etc.
To change the content of your Heroku app, just change the contents
of the repository, and push to master (push to the master branch of
the remote Heroku repository).
You will need to structure your repository carefully.
That's what this page is here to help you do.
Heroku can figure out the rest from there.

0
content/img/bunny.png → docs/img/bunny.png

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/img/warnico.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

BIN
docs/img/warning.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

92
docs/index.md

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
# github-heroku-attack-rabbits
## What's this business all about, then?
This repository helps you put access control into place to protect your secret pages by (deep breath):
hosting your secret page of static and/or dynamic content by using a free Heroku app
running a Python Flask server that uses Flask-Dance to authenticate visitors
with Github using OAuth which allows you fine-grained access control for your pages
using user attributes like organization or team membership or even things like how many
vowels a user has in their username.
Also, did I mention the attack rabbits?
![warning: attack rabbits ahead](img/warning.png)
## Where is everything?
Final pages:
* The finished product (pages on Heroku protected by attack rabbits)
is at [github-heroku-attack-rabbits.herokuapp.com](https://github-heroku-attack-rabbits.herokuapp.com)
* The documentation is at [pages.charlesreid1.com/github-heroku-attack-rabbits](https://pages.charlesreid1.com/github-heroku-attack-rabbits)
Two branches in this repo compose the github-heroku-attack-rabbits documentation:
* (**YOU ARE HERE**) The [docs](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs) branch
contains the files needed to generate the
[github-heroku-attack-rabbits documentation site](https://pages.charlesreid1.com/github-heroku-attack-rabbits).
* The [gh-pages](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/gh-pages) branch
contains the static files generated from the documentation.
The contents of this branch compose the
[github-heroku-attack-rabbits documentation site](https://pages.charlesreid1.com/github-heroku-attack-rabbits).
Two branches illustrate github-heroku-attack-rabbits in practice:
* The [secret](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/secret) branch contains the files needed to create the secret page.
This repository is public, so obviously these aren't *actually* secret,
but in practice this would be in a protected repository.
* The [heroku-pages](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/heroku-pages) branch
contains the content that is actually pushed to Heroku - that is,
the final Flask app.
## Contents
An overview of the steps:
[Get Started with Heroku](heroku.md)
[Get Started with Github](github.md)
[Initialize Repository: Branches](repo.md)
[Create a Flask App using Flask-Dance](flask.md)
* [Authenticate users based on Github membership only](flask_auth_github.md)
* [Authenticate users based on organization or team membership](flask_auth_org.md)
* [Authenticate users based on some other criteria](flask_auth_other.md)
* [Protection portions of the site](flask_auth_portions.md)
[Test Flask App Locally](flask_local.md)
[Deploying Flask App to Heroku](flask_heroku.md)
[Custom Domains](custom_domains.md)
## Links
Python software used:
* [Flask](http://flask.pocoo.org/)
* [Flask-dance](https://github.com/singingwolfboy/flask-dance)
* [Flask-dance-github](https://github.com/singingwolfboy/flask-dance-github)
* [mkdocs-material (documentation theme)](https://github.com/squidfunk/mkdocs-material)
* [mkdocs (documentation)](http://www.mkdocs.org/)
Commercial services:
* [Heroku](https://heroku.com)
* [Github](https://github.com)
## License
This is released under the [WTFPL](https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs/LICENSE).

110
docs/repo.md

@ -0,0 +1,110 @@ @@ -0,0 +1,110 @@
# Initialize Git Repository
Let's talk through how a repository should be laid out
if we're going to be hosting a Flask app on Heroku.
## Branches
We will need a minimum of two branches. Here we specify
the names that these branches will have in your Github repo,
**which is different from the names of the branches on Heroku**:
Branches on Github:
* `heroku-pages` - this branch contains the content that Heroku will host.
Specifically, it contains the Flask application in a `.py` file,
and a few other files to help Heroku determine how to run the app
and what to install.
* `master` - this branch contains the content used to generate the documentation
and page content that is being hosted behind the Heroku attack sheep.
The documentation you are reading right now is from the master branch,
and was made with `mkdocs`.
On Heroku, we only have a single branch:
* `master` (Heroku) maps to `heroku-pages` (Github)
## Repo Layout
Let's talk about the layout of the repository.
If you wish to build the site in order to deploy it to Heroku,
you should clone the `master` branch (preparing to make the
content for your attack sheep-protected page):
```
$ git clone -b master https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits.git
$ cd github-heroku-attack-rabbits
```
Once you are *inside* the master branch, clone the repo again,
but this time clone the `heroku-pages` branch, and clone it
to the `site/` folder:
```
$ git clone -b heroku-pages https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits.git site
$ cd site
```
Now you will want to set up the Heroku remote:
```
$ heroku git:remote -a my-cool-project
```
The layout should now be:
```
my-cool-project-repo/ <-- my-cool-project repo pointing to master branch
docs/ \
index.md |
heroku.md | <-- mkdocs files
... | (can use any static content generator:
| pelican, sphinx, etc.)
mkdocs.yml /
site/ <-- my-cool-project repo pointing to heroku-pages branch
Procfile \
github.py | <-- heroku python app files
requirements.txt | (can also use ruby, php, js, etc.)
runtime.txt | (can also use ruby, php, js, etc.)
... /
content/ \
index.html | <-- static content hosted by Flask
sitemap.xml |
... /
```
## Workflow
Once you have things set up according to the instructions and diagram above,
you're ready to run the push-to-deploy workflow and start running your secret
site on Heroku.

142
github.py

@ -1,142 +0,0 @@ @@ -1,142 +0,0 @@
import os, json
from werkzeug.contrib.fixers import ProxyFix
from flask import Flask, redirect, url_for, send_from_directory
from flask_dance.contrib.github import make_github_blueprint, github
from os.path import join, isfile, isdir
# helpful:
# https://tinyurl.com/yddsw4pg
PROJECT_ROOT = os.path.dirname(os.path.realpath(__file__))
STATIC_PATH = 'content'
app = Flask(__name__)
# this worked locally, but not on heroku
app.wsgi_app = ProxyFix(app.wsgi_app)
app.secret_key = os.environ.get("FLASK_SECRET_KEY", "9502861d41e8729c5cae3225920b1b46")
app.config["RESULT_STATIC_PATH"] = STATIC_PATH #os.path.join(PROJECT_ROOT,STATIC_PATH)
app.config["GITHUB_OAUTH_CLIENT_ID"] = os.environ.get("GITHUB_OAUTH_CLIENT_ID")
app.config["GITHUB_OAUTH_CLIENT_SECRET"] = os.environ.get("GITHUB_OAUTH_CLIENT_SECRET")
github_bp = make_github_blueprint(
client_id = os.environ.get('GITHUB_OAUTH_CLIENT_ID'),
client_secret = os.environ.get('GITHUB_OAUTH_CLIENT_SECRET'),
scope='read:org')
app.register_blueprint(github_bp, url_prefix="/login")
contents200 = """
<html><body>
<div class="header header-20x">
<h1>Status: OK 200</h1>
</div>
<div class="body attack attack-rabbits">
<p>You found a public page, so you're safe for now. Run away while you still can.</p>
<p>Otherwise... the attack rabbits may find you yet.</p>
<img src="../img/warning.png" />
</div>
</body></html>
"""
contents403 = """
<html><body>
<div class="header header-40x">
<h1>Status: Error 403 Access Denied</h1>
</div>
<div class="body attack attack-rabbits">
<p>Access Denied!</p>
<p>Attack rabbits, attack! Prepare to meet your fate,
sneaky unauthorized intruder, at the hands of one of
the nastiest, most horrible, gnashing teeth, and fangs,
and little claws like daggers -</p>
<img src="../img/attack-rabbits.png" />
</div>
</body></html>
"""
contents404 = """
<html><body>
<div class="header header-40x">
<h1>Status: Error 404 Page Not Found</h1>
</div>
<div class="body attack attack-rabbits">
<p>The resource you requested could not be found.</p>
<p>The attack rabbits are circling, eyeing you suspiciously.</p>
<img src="../img/attack-rabbit.png" />
</div>
</body></html>
"""
@app.route('/')
def index():
"""
the index, anybody can use
"""
return send_from_directory(STATIC_PATH, 'index.html')
@app.route('/fishslap/')
def fishslap_even():
if not github.authorized:
return redirect(url_for("github.login"))
resp = github.get("/user")
if resp.ok:
username = resp.json()['login']
if even_vowels(username):
#return "Hello {username}".format(username=username)
fishslap = os.path.join(STATIC_PATH,'fishslap')
return send_from_directory(fishslap, 'index.html')
return contents403
@app.route('/sillywalk/')
def sillywalk_odd():
if not github.authorized:
return redirect(url_for("github.login"))
resp = github.get("/user")
if resp.ok:
username = resp.json()['login']
if not even_vowels(username):
#return "Hello {username}".format(username=username)
sillywalk = os.path.join(STATIC_PATH,'sillywalk')
return send_from_directory(sillywalk, 'index.html')
return contents403
@app.route('/<path:path>')
def all_github_users_welcome(path):
"""
all other paths are public for anybody too
"""
return send_from_directory(STATIC_PATH, path)
@app.errorhandler(404)
def oops(e):
return contents404
def even_vowels(my_string):
"""
Boolean: are there an even number of vowels in my_string?
"""
i = 0
for c in my_string:
if c in list('aeiou'):
i += 1
if i%2==0:
return True
else:
return False
if __name__ == "__main__":
app.run()

1
mkdocs-material

@ -0,0 +1 @@ @@ -0,0 +1 @@
Subproject commit ff95dcb8463eb5f8f65b14c3d145afae21671ad9

50
mkdocs.yml

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
site_name: github-heroku-attack-rabbits
site_url: https://pages.charlesreid1.com/github-heroku-attack-rabbits
repo_name: charlesreid1/github-heroku-attack-rabbits
repo_url: https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits
edit_uri: ""
pages:
- 'Index' : 'index.md'
- 'Get Started with Heroku': 'heroku.md'
- 'Get Started with Github': 'github.md'
- 'Initialize Repository: Branches': 'repo.md'
- 'Create a Flask App using Flask-Dance': 'flask.md'
- 'Flask':
- 'Authenticate users based on Github membership only': 'flask_auth_github.md'
- 'Authenticate users based on organization or team membership': 'flask_auth_org.md'
- 'Authenticate users based on some other criteria': 'flask_auth_other.md'
- 'Protection portions of the site': 'flask_auth_portions.md'
- 'Test Flask App Locally': 'flask_local.md'
- 'Deploying Flask App to Heroku': 'flask_heroku.md'
- 'Custom Domains': 'custom_domains.md'
copyright: 'Copyright &copy; 2018 Charles Reid, released under the <a href="https://git.charlesreid1.com/charlesreid1/github-heroku-attack-rabbits/src/branch/docs/LICENSE">WTFPL</a>.<br /><br />Many Bothans died to bring us this documentation.<br /><br />'
docs_dir: docs
site_dir: site
theme:
name: null
custom_dir: 'mkdocs-material/material'
# pretty colors! see https://squidfunk.github.io/mkdocs-material/getting-started/#primary-colors
palette:
primary: 'blue grey'
accent: 'blue grey'
logo: 'img/bunny.png'
### # fun logos! see https://material.io/icons/
### logo:
### icon: 'lock'
font:
text: 'Questrial'
code: 'Roboto Mono'
# this will add docs/css/custom.css to all your docs
extra_css:
- css/custom.css

2
requirements.txt

@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
flask-dance
gunicorn

1
runtime.txt

@ -1 +0,0 @@ @@ -1 +0,0 @@
python-3.6.5
Loading…
Cancel
Save