repointing users at supported repo
11
.awsbox.json
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"processes": [
|
||||
"server/bin/clortho"
|
||||
],
|
||||
"env": {
|
||||
"CONFIG_FILES": "$HOME/config.json"
|
||||
},
|
||||
"hooks": {
|
||||
"postdeploy": "scripts/gen_keys.js"
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
.DS_Store
|
||||
/server/config/*.json
|
||||
/server/config/*.pem
|
||||
/server/config/*.csr
|
||||
/server/var
|
||||
*~
|
||||
#*#
|
||||
locale
|
||||
/node_modules
|
||||
/rpmbuild
|
27
.jshintrc
|
@ -1,27 +0,0 @@
|
|||
{
|
||||
"passfail": false,
|
||||
"maxerr": 100,
|
||||
"node": true,
|
||||
"forin": false,
|
||||
"boss": true,
|
||||
"noarg": true,
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"browser": true,
|
||||
"laxbreak": true,
|
||||
"laxcomma": true,
|
||||
"eqeqeq": true,
|
||||
"eqnull": true,
|
||||
"expr": true,
|
||||
"indent": 2,
|
||||
"white": false,
|
||||
"predef": [
|
||||
"exports",
|
||||
"require",
|
||||
"process"
|
||||
],
|
||||
"esnext": true,
|
||||
"shadow": false,
|
||||
"supernew": false,
|
||||
"strict": false
|
||||
}
|
16
.travis.yml
|
@ -1,16 +0,0 @@
|
|||
language: node_js
|
||||
|
||||
before_install:
|
||||
- sudo apt-get install libgmp3-dev
|
||||
|
||||
before_script:
|
||||
- scripts/gen_keys.js
|
||||
|
||||
node_js:
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- lloyd@hilaiel.com
|
||||
- bwong@mozilla.com
|
31
ChangeLog
|
@ -1,31 +0,0 @@
|
|||
rel2014_06_20.19.26.40:
|
||||
|
||||
* b75c6ba Get rid of merge commits in the changelog
|
||||
* 6688d8b Add a Do Not Track policy
|
||||
* 0c2612f Adding some missing properties to the package.json file
|
||||
* c032b4b remove superfluous argument to getUserData()
|
||||
* 2246335 refactor auth.js, improved command line testing tool, removed unneccesary LDAP reconnection
|
||||
* 34c6637 fix unit tests, update pwdChangeTime to pwdChangedTime
|
||||
* d734bc5 Fix for issue #114
|
||||
|
||||
rel2013_08_28.15.22.40:
|
||||
|
||||
* 7129101 changed pwdChangeTime to pwdChangedTime
|
||||
* 29fa434 Improve documentation on new release process, #111
|
||||
* 25f4285 Remove old train/stage tag create scripts #111
|
||||
* eb8979b update useragent to 2.0.7 to detect IE11
|
||||
* 6140471 added bigint lockdown dependency back in
|
||||
* fd8f2dc Detect 3rd party cookies and show an error screen
|
||||
* 85f61de Make email address field read only after password manager runs. Fixes Issue #104
|
||||
* e89639b #91 old session destroyed on password change
|
||||
* 5657e90 fixes #91 User should be logged out when LDAP password changes
|
||||
|
||||
rel2013_08_06.15.01.46:
|
||||
|
||||
* 9a8ad5e Fix bugs in create-release.sh #111
|
||||
* b41df8c Rename create-release-tag.sh to create-release.sh #111
|
||||
* 737770d Add ChangeLog creation to create-release-tag.sh
|
||||
* 5ea2b29 update readme
|
||||
* ec24abf fix i18n-abide lockdown issues
|
||||
* 794c302 #105 add P3P cookie if talking to IE browsers
|
||||
|
37
README.md
|
@ -1,36 +1 @@
|
|||
[![Build
|
||||
Status](https://travis-ci.org/mozilla/vinz-clortho.png?branch=master)](https://travis-ci.org/mozilla/vinz-clortho)
|
||||
|
||||
# Mozilla IdP
|
||||
|
||||
``mozilla-idp`` is a server that implements support for Persona on the
|
||||
mozilla.com domain.
|
||||
|
||||
When deployed, this will allow mozillans with `mozilla.com` or
|
||||
`mozillafoundation.org` email addresses to authenticate with Persona enabled
|
||||
websites using their Mozilla (LDAP) password.
|
||||
|
||||
## Getting Code to Production
|
||||
|
||||
This is the process for getting new code into Production
|
||||
|
||||
1. Do features and bug fixes in branches. Create a pull request to have new
|
||||
code merged into the `master` branch
|
||||
1. Create a new release tag with `scripts/create-release.sh`
|
||||
1. Create an RPM from the tag
|
||||
1. Create a new staging server based on the new RPM
|
||||
1. Have QA test to make sure everything is OK
|
||||
1. *if* tests pass, create new production systems from same version. Otherwise
|
||||
go back to step 1. to fix issues.
|
||||
|
||||
## Why the RPM?
|
||||
|
||||
This is a quick introduction to how the Service Ops. team deploys Mozilla IdP.
|
||||
For security and operational reasons we turn the application into an RPM and
|
||||
deploy from our private RPM repository.
|
||||
|
||||
This allows us to maintain a package that can be audited as well as very
|
||||
specific versioning using RPMs.
|
||||
|
||||
The scripts and processes for building the RPM exists in the
|
||||
mozilla-services/svcops-oompaloompas repository.
|
||||
Moved to [mozilla-services/mozilla-idp](https://github.com/mozilla-services/mozilla-idp)
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
# Developer Notes
|
||||
|
||||
## Running locally
|
||||
|
||||
### Set up Configuration
|
||||
|
||||
copy server/config/local.json-dist to local.json and edit a few fields:
|
||||
|
||||
...
|
||||
"ldap_bind_dn": "mail=USERNAME@mozilla.com,o=com,dc=mozilla",
|
||||
...
|
||||
"ldap_server_url": "ldap://localhost:1389",
|
||||
...
|
||||
|
||||
### Starting the Mock LDAP server
|
||||
|
||||
$ QAUser=test QAPass=test node mock-ldap-server/server.js
|
||||
|
||||
This mock server runs LDAP on localhost:1389 and a status HTTP server on localhost:3001.
|
||||
|
||||
### Starting Vinz Clortho
|
||||
|
||||
$ PORT=3002 ADDRESS=0.0.0.0 npm start
|
||||
|
||||
### One Time Setup
|
||||
|
||||
We need our well-known to be on the file system. You can put this anywhere, but we'll use this when starting BrowserID servers.
|
||||
|
||||
$ curl http://localhost:3002/.well-known/browserid > [LOCAL_PATH_TO_WELL_KNOWN_FILE]
|
||||
|
||||
### Starting BrowserID
|
||||
|
||||
In your local browserid, set `SHIMMED_PRIMARIES`. Example:
|
||||
|
||||
$ cd ../browserid
|
||||
$ SHIMMED_PRIMARIES="mozilla.com|http://127.0.0.1:3002|[LOCAL_PATH_TO_WELL_KNOWN_FILE]" npm start
|
||||
|
||||
You can now use `user1@mozilla.com` with password testtest and Vinz Clortho will handle the sign in flow.
|
||||
|
||||
Note: 127.0.0.1 should be an IP address that your web browser can use to hit your instance of Vinz Clortho. If your using VMs, then make it an IP for the VM.
|
Двоичные данные
docs/aws-infrastructure.png
До Ширина: | Высота: | Размер: 32 KiB |
|
@ -1,32 +0,0 @@
|
|||
This project uses statsd to report application level metrics.
|
||||
|
||||
The goal of statsd is to help you verify the application is
|
||||
running correctly, and to facilitate problem isolation when
|
||||
it's not.
|
||||
|
||||
Here is the statsd hierarchy:
|
||||
|
||||
* `mozillaidp` - top level bucket for all stats
|
||||
* `ldap` - stats related to communication with LDAP
|
||||
* `error` - counter of specific types of LDAP errors
|
||||
* `auth` - counter of authentication attempts (success & failure)
|
||||
* `timing` - granular timing for interactions with LDAP
|
||||
* `server` - high level application events
|
||||
* `uncaught_exception` - a js exception was thrown that wasn't caught
|
||||
* `started` - the server started up
|
||||
* `healthcheck` - status of health check when requested by load balancers
|
||||
* `ok`
|
||||
* `error`
|
||||
* `provision` - stats about certificate provisioning
|
||||
* `attempt` - HTML provisioning content was served
|
||||
* `success` - certificate provisioning succeeded
|
||||
* `auth` - stats about user authenticatoin
|
||||
* `attempt` - HTML authentication content was served
|
||||
* `throttle` - authentication failed due to throttling
|
||||
* `success` - authentication succeeded and session was established
|
||||
|
||||
|
||||
The ultimate source of truth is always the code, but an effort
|
||||
should be made to keep this document up to date to describe the general
|
||||
hierarchy so that statsd data is discoverable and easy to use as a
|
||||
problem isolation tool.
|
474
lockdown.json
|
@ -1,474 +0,0 @@
|
|||
{
|
||||
"JSONSelect": {
|
||||
"0.4.0": "a08edcc67eb3fcbe99ed630855344a0cf282bb8d"
|
||||
},
|
||||
"abbrev": {
|
||||
"1.0.4": "bd55ae5e413ba1722ee4caba1f6ea10414a59ecd"
|
||||
},
|
||||
"amdefine": {
|
||||
"0.0.5": "86b6e9470f8cde955ef7daa3cf5d544ba81aa3db"
|
||||
},
|
||||
"ansi": {
|
||||
"0.1.2": "2627e29498f06e2a1c2ece9c21e28fd494430827"
|
||||
},
|
||||
"asn1": {
|
||||
"0.1.11": "559be18376d08a4ec4dbe80877d27818639b2df7"
|
||||
},
|
||||
"assert-plus": {
|
||||
"0.1.2": "d93ffdbb67ac5507779be316a7d65146417beef8"
|
||||
},
|
||||
"async": {
|
||||
"0.1.22": "0fc1aaa088a0e3ef0ebe2d8831bab0dcf8845061",
|
||||
"0.2.9": "df63060fbf3d33286a76aaf6d55a2986d9ff8619"
|
||||
},
|
||||
"aws-lib": {
|
||||
"0.0.5": "971b8995078d83c80f2372f134c496e71b293a46"
|
||||
},
|
||||
"aws-sign": {
|
||||
"0.2.0": "c55013856c8194ec854a0cbec90aab5a04ce3ac5"
|
||||
},
|
||||
"awsbox": {
|
||||
"0.4.5": "027a9998955f09f3d6b0a10d38ea37ae381b6358"
|
||||
},
|
||||
"bigint": {
|
||||
"0.4.2": "5bccafb5af3924cc71eb59942fb3a64a12906e49"
|
||||
},
|
||||
"boom": {
|
||||
"0.3.8": "c8cdb041435912741628c044ecc732d1d17c09ea"
|
||||
},
|
||||
"browserify": {
|
||||
"1.13.5": "b5f0a160733779d27547885dfb598a65ef6fdaad"
|
||||
},
|
||||
"buffertools": {
|
||||
"1.1.0": "c1bc946a3cce722c9adaaed505e9896aa171be9c"
|
||||
},
|
||||
"bunyan": {
|
||||
"0.21.1": "ea00a0d5223572e31e1e71efba2237cb1915942a"
|
||||
},
|
||||
"cef": {
|
||||
"0.3.3": "74a521e3c5c702fe1dc240e6250a2583f885227f"
|
||||
},
|
||||
"character-parser": {
|
||||
"1.0.2": "55384d6afcf8c6b9dd483e8347646a790e4545e7"
|
||||
},
|
||||
"cjson": {
|
||||
"0.2.1": "73cd8aad65d9e1505f9af1744d3b79c1527682a5"
|
||||
},
|
||||
"cli": {
|
||||
"0.4.4-2": "a38c8f991df22eea0179ec16dd90fa3f3c85fa4a"
|
||||
},
|
||||
"client-sessions": {
|
||||
"0.3.0": "6c9993858b2009416247ae70051b88a1d5e6ee7d"
|
||||
},
|
||||
"coffee-script": {
|
||||
"1.6.3": "6355d32cf1b04cdff6b484e5e711782b2f0c39be"
|
||||
},
|
||||
"colors": {
|
||||
"0.5.1": "7d0023eaeb154e8ee9fce75dcb923d0ed1667774",
|
||||
"0.6.0-1": "6dbb68ceb8bc60f2b313dcc5ce1599f06d19e67a"
|
||||
},
|
||||
"combined-stream": {
|
||||
"0.0.4": "2d1a43347dbe9515a4a2796732e5b88473840b22"
|
||||
},
|
||||
"commander": {
|
||||
"0.6.1": "fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06",
|
||||
"1.1.1": "50d1651868ae60eccff0a2d9f34595376bc6b041"
|
||||
},
|
||||
"commondir": {
|
||||
"0.0.1": "89f00fdcd51b519c578733fec563e6a6da7f5be2"
|
||||
},
|
||||
"connect": {
|
||||
"1.9.2": "42880a22e9438ae59a8add74e437f58ae8e52807"
|
||||
},
|
||||
"connect-fonts": {
|
||||
"0.0.9": "3233b93bb78b6c7c1818892b90fd779c83447d84"
|
||||
},
|
||||
"connect-fonts-opensans": {
|
||||
"0.0.4": "184ce9f26b681a10a916268b1f80c4b6c9450e71"
|
||||
},
|
||||
"console-browserify": {
|
||||
"0.1.6": "d128a3c0bb88350eb5626c6e7c71a6f0fd48983c"
|
||||
},
|
||||
"convict": {
|
||||
"0.1.0": "d4b1948235196091855aaedc175374c58ae212d8"
|
||||
},
|
||||
"cookie-jar": {
|
||||
"0.2.0": "64ecc06ac978db795e4b5290cbe48ba3781400fa"
|
||||
},
|
||||
"cookies": {
|
||||
"0.2.1": "fee635ef023704893ac30387899f3dc448a52840"
|
||||
},
|
||||
"cryptiles": {
|
||||
"0.1.3": "1a556734f06d24ba34862ae9cb9e709a3afbff1c"
|
||||
},
|
||||
"crypto-browserify": {
|
||||
"0.2.3": "c98141505d90e31a1e456cb97343dc3b0f4a1a2a"
|
||||
},
|
||||
"css": {
|
||||
"1.0.8": "9386811ca82bccc9ee7fb5a732b1e2a317c8a3e7"
|
||||
},
|
||||
"css-parse": {
|
||||
"1.0.4": "38b0503fbf9da9f54e9c1dbda60e145c77117bdd"
|
||||
},
|
||||
"css-stringify": {
|
||||
"1.0.5": "b0d042946db2953bb9d292900a6cb5f6d0122031"
|
||||
},
|
||||
"cycle": {
|
||||
"1.0.2": "269aca6f1b8d2baeefc8ccbc888b459f322c4e60"
|
||||
},
|
||||
"dateformat": {
|
||||
"1.0.2-1.2.3": "b0220c02de98617433b72851cf47de3df2cdbee9"
|
||||
},
|
||||
"debug": {
|
||||
"0.7.2": "056692c86670977f115de82917918b8e8b9a10f0"
|
||||
},
|
||||
"delayed-stream": {
|
||||
"0.0.5": "d4b1f43a93e8296dfe02694f4680bc37a313c73f"
|
||||
},
|
||||
"deputy": {
|
||||
"0.0.4": "edc00a9ef5c53527c405328534c99795ada41cbf"
|
||||
},
|
||||
"detective": {
|
||||
"0.1.1": "f1e04fe973754c8907ae51edd3e230e380d76fe9",
|
||||
"0.2.1": "9ce92601fd223810c29432ad034f8c62d8b8654f"
|
||||
},
|
||||
"diff": {
|
||||
"1.0.2": "4ae73f1aee8d6fcf484f1a1ce77ce651d9b7f0c9"
|
||||
},
|
||||
"dtrace-provider": {
|
||||
"0.2.8": "e243f19219aa95fbf0d8f2ffb07f5bd64e94fe20"
|
||||
},
|
||||
"ejs": {
|
||||
"0.6.1": "16ccc98eeeac166982927fa67eb3fca8865f6871"
|
||||
},
|
||||
"escodegen": {
|
||||
"0.0.23": "9acf978164368e42276571f18839c823b3a844df"
|
||||
},
|
||||
"esprima": {
|
||||
"0.9.9": "1b90925c975d632d7282939c3bb9c3a423c30490",
|
||||
"1.0.3": "7bdb544f95526d424808654d3b8fbe928650c0fe"
|
||||
},
|
||||
"estraverse": {
|
||||
"0.0.4": "01a0932dfee574684a598af5a67c3bf9b6428db2",
|
||||
"1.1.2-1": "28ff59bb3cf1272a2027e1c1541f94b23226b311"
|
||||
},
|
||||
"express": {
|
||||
"2.5.11": "4ce8ea1f3635e69e49f0ebb497b6a4b0a51ce6f0"
|
||||
},
|
||||
"extsprintf": {
|
||||
"1.0.0": "4d58b815ace5bebfc4ebf03cf98b0a7604a99b86"
|
||||
},
|
||||
"eyes": {
|
||||
"0.1.8": "62cf120234c683785d902348a800ef3e0cc20bc0"
|
||||
},
|
||||
"filed": {
|
||||
"0.1.0": "b0f626472a2366dc1194537a4eea7e7a89f3c735"
|
||||
},
|
||||
"forEachAsync": {
|
||||
"2.2.0": "093b32ce868cb69f5166dcf331fae074adc71cee"
|
||||
},
|
||||
"forever-agent": {
|
||||
"0.2.0": "e1c25c7ad44e09c38f233876c76fcc24ff843b1f"
|
||||
},
|
||||
"form-data": {
|
||||
"0.0.10": "db345a5378d86aeeb1ed5d553b869ac192d2f5ed"
|
||||
},
|
||||
"formidable": {
|
||||
"1.0.14": "2b3f4c411cbb5fdd695c44843e2a23514a43231a"
|
||||
},
|
||||
"github-url-from-git": {
|
||||
"1.1.1": "1f89623453123ef9623956e264c60bf4c3cf5ccf"
|
||||
},
|
||||
"glob": {
|
||||
"3.2.6": "28c805b47bc6c19ba3059cbdf079b98ff62442f2"
|
||||
},
|
||||
"gobbledygook": {
|
||||
"0.0.3": "437bb23d3ade04dd26b49e7c21e4026c6b086234"
|
||||
},
|
||||
"graceful-fs": {
|
||||
"1.1.14": "07078db5f6377f6321fceaaedf497de124dc9465",
|
||||
"1.2.3": "15a4806a57547cb2d2dbf27f42e89a8c3451b364"
|
||||
},
|
||||
"growl": {
|
||||
"1.7.0": "de2d66136d002e112ba70f3f10c31cf7c350b2da"
|
||||
},
|
||||
"hawk": {
|
||||
"0.10.2": "9b361dee95a931640e6d504e05609a8fc3ac45d2"
|
||||
},
|
||||
"hoek": {
|
||||
"0.7.6": "60fbd904557541cd2b8795abf308a1b3770e155a"
|
||||
},
|
||||
"http-browserify": {
|
||||
"0.1.1": "d9d82735a5f85f950761ac3909ba9485ec0af4f1"
|
||||
},
|
||||
"i18n-abide": {
|
||||
"0.0.11": "0335f84ab550de1fc211b3169a105ac573f6efcc"
|
||||
},
|
||||
"inherits": {
|
||||
"2.0.0": "76c81b3b1c10ddee3a60bf2c247162bc369f8ba8"
|
||||
},
|
||||
"is-promise": {
|
||||
"1.0.0": "b998d17551f16f69f7bd4828f58f018cc81e064f"
|
||||
},
|
||||
"jade": {
|
||||
"0.26.3": "8f10d7977d8d79f2f6ff862a81b0513ccb25686c",
|
||||
"0.30.0": "9ea816da61bea6fef4709b4d9f6b442f1e205ba3"
|
||||
},
|
||||
"jshint": {
|
||||
"2.1.2": "1fb7b77df7b8620c17d4f807945bb2d24990969e"
|
||||
},
|
||||
"json-schema": {
|
||||
"0.2.2": "50354f19f603917c695f70b85afa77c3b0f23506"
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"3.0.0": "9db7b0e530c7f289c5e8c8432af191c2ff75a5b3"
|
||||
},
|
||||
"jsprim": {
|
||||
"0.3.0": "cd13466ea2480dbd8396a570d47d31dda476f8b1"
|
||||
},
|
||||
"jsxgettext": {
|
||||
"0.1.3": "db9dd0bedd531606cdd5fd978dd75d383fd99327"
|
||||
},
|
||||
"jwcrypto": {
|
||||
"0.4.3": "b48ef3db136b09df0933bfa09c717fd738b56038"
|
||||
},
|
||||
"keypress": {
|
||||
"0.1.0": "4a3188d4291b66b4f65edb99f806aa9ae293592a"
|
||||
},
|
||||
"ldapjs": {
|
||||
"0.6.3": "*"
|
||||
},
|
||||
"lockdown": {
|
||||
"0.0.5": "6bbcc01b7d2a3894a712f135b2675a078ede24c7"
|
||||
},
|
||||
"lru-cache": {
|
||||
"2.2.4": "6c658619becf14031d0d0b594b16042ce4dc063d",
|
||||
"2.3.0": "1cee12d5a9f28ed1ee37e9c332b8888e6b85412a"
|
||||
},
|
||||
"mersenne": {
|
||||
"0.0.3": "c39a3d45fee6091189ccd329107312ea8fe14d8a"
|
||||
},
|
||||
"mime": {
|
||||
"1.2.10": "066380acbc3d78d4f4a51004d8988425dc68b9b1",
|
||||
"1.2.4": "11b5fdaf29c2509255176b80ad520294f5de92b7",
|
||||
"1.2.9": "009cd40867bd35de521b3b966f04e2f8d4d13d09"
|
||||
},
|
||||
"minimatch": {
|
||||
"0.2.12": "ea82a012ac662c7ddfaa144f1c147e6946f5dafb"
|
||||
},
|
||||
"minimist": {
|
||||
"0.0.2": "3297e0500be195b8fcb56668c45b925bc9bca7ab"
|
||||
},
|
||||
"mkdirp": {
|
||||
"0.3.0": "1bbf5ab1ba827af23575143490426455f481fe1e",
|
||||
"0.3.3": "595e251c1370c3a68bab2136d0e348b8105adf13",
|
||||
"0.3.5": "de3e5f8961c88c787ee1368df849ac4413eca8d7"
|
||||
},
|
||||
"mocha": {
|
||||
"1.9.0": "141054b13cb03ce5ce59aece3d65d5ca01b8df0a"
|
||||
},
|
||||
"moment": {
|
||||
"1.7.2": "e66be344be2e9ec1d12f1e16a8ca49bf63417f4f"
|
||||
},
|
||||
"monocle": {
|
||||
"0.1.50": "9a7cbd0ccc10de95fd78a04b9beb2482ae4940b7"
|
||||
},
|
||||
"ms": {
|
||||
"0.3.0": "03edc348d613e66a56486cfdac53bcbe899cbd61"
|
||||
},
|
||||
"mv": {
|
||||
"0.0.5": "15eac759479884df1131d6de56bce20b654f5391"
|
||||
},
|
||||
"negotiator": {
|
||||
"0.2.6": "28db6bc2e442c8655325d156ff74055dc0db289c"
|
||||
},
|
||||
"node-font-face-generator": {
|
||||
"0.1.0-dev1": "778430ea7fb61ed9de61e8a51e0aadda29007193"
|
||||
},
|
||||
"node-proxy": {
|
||||
"0.6.0": "41e64712dbd947aa9d9f466b5c0b5ee020bbcbbb"
|
||||
},
|
||||
"node-statsd": {
|
||||
"0.0.2": "*"
|
||||
},
|
||||
"node-uuid": {
|
||||
"1.4.0": "07f9b2337572ff6275c775e1d48513f3a45d7a65"
|
||||
},
|
||||
"nomnom": {
|
||||
"1.5.2": "f4345448a853cfbd5c0d26320f2477ab0526fe2f"
|
||||
},
|
||||
"nopt": {
|
||||
"2.1.1": "91eb7c4b017e7c00adcad1fd6d63944d0fdb75c1"
|
||||
},
|
||||
"normalize-package-data": {
|
||||
"0.1.7": "f216a318a868214892e89ad80a944cddc1a79615"
|
||||
},
|
||||
"npmlog": {
|
||||
"0.0.4": "a12a7418606b7e0183a2851d97a8729b9a0f3837"
|
||||
},
|
||||
"nub": {
|
||||
"0.0.0": "b369bd32bdde66af59605c3b0520bc219dccc04f"
|
||||
},
|
||||
"oauth-sign": {
|
||||
"0.2.0": "a0e6a1715daed062f322b622b7fe5afd1035b6e2"
|
||||
},
|
||||
"once": {
|
||||
"1.1.1": "9db574933ccb08c3a7614d154032c09ea6f339e7"
|
||||
},
|
||||
"oppressor": {
|
||||
"0.0.1": "84b2058c9121c4c6fe632566b31ed39618bd8feb"
|
||||
},
|
||||
"optimist": {
|
||||
"0.2.6": "c15b750c98274ea175d241b745edf4ddc88f177b",
|
||||
"0.3.1": "6680d30560193af5a55eb64394883ed7bcb98f2e",
|
||||
"0.3.4": "4d6d0bd71ffad0da4ba4f6d876d5eeb04e07480b",
|
||||
"0.3.7": "c90941ad59e4273328923074d2cf2e7cbc6ec0d9",
|
||||
"0.6.0": "69424826f3405f79f142e6fc3d9ae58d4dbb9200"
|
||||
},
|
||||
"osenv": {
|
||||
"0.0.3": "cd6ad8ddb290915ad9e22765576025d411f29cb6"
|
||||
},
|
||||
"pkginfo": {
|
||||
"0.3.0": "726411401039fe9b009eea86614295d5f3a54276"
|
||||
},
|
||||
"plist": {
|
||||
"0.4.3": "812842a873aa57f98d0050bee6aaf3549b9d5798"
|
||||
},
|
||||
"pooling": {
|
||||
"0.4.4": "d06d7f565e9a1eec8b60bfa259c1e958a9e6ba03"
|
||||
},
|
||||
"promise": {
|
||||
"2.0.0": "46648aa9d605af5d2e70c3024bf59436da02b80e"
|
||||
},
|
||||
"qs": {
|
||||
"0.4.2": "3cac4c861e371a8c9c4770ac23cda8de639b8e5f",
|
||||
"0.5.6": "31b1ad058567651c526921506b9a8793911a0384"
|
||||
},
|
||||
"read-installed": {
|
||||
"0.0.1": "2d9b9086ae33ae42793210f519701169edabd2e2"
|
||||
},
|
||||
"read-package-json": {
|
||||
"0.4.1": "a0e917cce8333444889543de9db2b1a4606baaa2"
|
||||
},
|
||||
"readdirp": {
|
||||
"0.2.5": "c4c276e52977ae25db5191fe51d008550f15d9bb"
|
||||
},
|
||||
"relative-date": {
|
||||
"1.1.1": "75c97c5446fa1146c1d250c47ca3629fb9a2e764"
|
||||
},
|
||||
"request": {
|
||||
"2.16.6": "872fe445ae72de266b37879d6ad7dc948fa01cad"
|
||||
},
|
||||
"resolve": {
|
||||
"0.2.8": "fdb17d4abb0ecaf6f80d67ac03cf290088f6c0d0"
|
||||
},
|
||||
"response-stream": {
|
||||
"0.0.0": "da4b17cc7684c98c962beb4d95f668c8dcad09d5"
|
||||
},
|
||||
"rimraf": {
|
||||
"2.1.4": "5a6eb62eeda068f51ede50f29b3e5cd22f3d9bb2"
|
||||
},
|
||||
"sax": {
|
||||
"0.1.5": "d1829a6120fa01665eb4dbff6c43f29fd6d61471",
|
||||
"0.5.4": "a3a4e1a9cf182bb547156c5232a49a1c3732ff7d"
|
||||
},
|
||||
"semver": {
|
||||
"1.0.14": "cac5e2d55a6fbf958cb220ae844045071c78f676"
|
||||
},
|
||||
"sequence": {
|
||||
"2.2.1": "7f5617895d44351c0a047e764467690490a16b03"
|
||||
},
|
||||
"shelljs": {
|
||||
"0.1.4": "dfbbe78d56c3c0168d2fb79e10ecd1dbcb07ec0e"
|
||||
},
|
||||
"should": {
|
||||
"1.2.2": "0f03f775066d9ea2632690c917b12824fcc1d582"
|
||||
},
|
||||
"sigmund": {
|
||||
"1.0.0": "66a2b3a749ae8b5fb89efd4fcc01dc94fbe02296"
|
||||
},
|
||||
"slide": {
|
||||
"1.1.4": "2b23f1949b369ed61d22bd6570ff0320302fc8df"
|
||||
},
|
||||
"sntp": {
|
||||
"0.1.4": "5ef481b951a7b29affdf4afd7f26838fc1120f84"
|
||||
},
|
||||
"source-map": {
|
||||
"0.1.27": "f114e06a8b5c05cbc51aa1fa600e728162455eda"
|
||||
},
|
||||
"stack-trace": {
|
||||
"0.0.7": "c72e089744fc3659f508cdce3621af5634ec0fff"
|
||||
},
|
||||
"temp": {
|
||||
"0.4.0": "671ad63d57be0fe9d7294664b3fc400636678a60",
|
||||
"0.5.1": "77ab19c79aa7b593cbe4fac2441768cad987b8df"
|
||||
},
|
||||
"through": {
|
||||
"0.1.4": "8059576d833089b3c18eccd37580bf9cc5b59130"
|
||||
},
|
||||
"tmp": {
|
||||
"0.0.16": "704ea0513c15375389ef5b6dde865730528f2245"
|
||||
},
|
||||
"transformers": {
|
||||
"2.0.1": "352131dfceb93a7532dc7535a4f142510435a394"
|
||||
},
|
||||
"tunnel-agent": {
|
||||
"0.2.0": "6853c2afb1b2109e45629e492bde35f459ea69e8"
|
||||
},
|
||||
"ua-parser": {
|
||||
"0.2.4": "c25ef577be95350d0fe1d3361597282da4e9ba71"
|
||||
},
|
||||
"uglify-js": {
|
||||
"1.0.6": "f0d3aafd463f26a437b9ebc19f4947ab7e8078aa",
|
||||
"1.2.6": "d354b2d3c1cf10ebc18fa78c11a28bdd9ce1580d",
|
||||
"2.2.5": "a6e02a70d839792b9780488b7b8b184c095c99c7"
|
||||
},
|
||||
"underscore": {
|
||||
"1.1.7": "40bab84bad19d230096e8d6ef628bff055d83db0",
|
||||
"1.4.4": "61a6a32010622afa07963bf325203cf12239d604"
|
||||
},
|
||||
"urlparse": {
|
||||
"0.0.1": "d171ec4681fcd0d8bd00b64345637d89a9700876"
|
||||
},
|
||||
"useragent": {
|
||||
"2.0.7": "a44c07d157a15e13d73d4af4ece6aab70f298224"
|
||||
},
|
||||
"validator": {
|
||||
"0.4.24": "9d88e88809114a861ec69bc0ddcb85322c98499a",
|
||||
"1.1.3": "0ed8cb7c3b0b8852bb853b09ef8fcde1d1d4545b"
|
||||
},
|
||||
"vasync": {
|
||||
"1.3.3": "84917680717020b67e043902e63bc143174c8728"
|
||||
},
|
||||
"verror": {
|
||||
"1.1.0": "2a4b4eb14a207051e75a6f94ee51315bf173a1b0",
|
||||
"1.3.3": "8a6a4ac3a8c774b6f687fece49bdffd78552e2cd"
|
||||
},
|
||||
"vm-browserify": {
|
||||
"0.0.1": "51d25979366ab219dfe35a3fc65ecd6af3631d54"
|
||||
},
|
||||
"vows": {
|
||||
"0.5.13": "f6fd9ee9c36d3f20bd6680455cff8090c4b29cde"
|
||||
},
|
||||
"walk": {
|
||||
"2.2.1": "5ada1f8e49e47d4b7445d8be7a2e1e631ab43016"
|
||||
},
|
||||
"winston": {
|
||||
"0.7.1": "e291ab24eddbf79ea40ff532619277a0d30b0eb3"
|
||||
},
|
||||
"wordwrap": {
|
||||
"0.0.2": "b79669bb42ecb409f83d583cad52ca17eaa1643f"
|
||||
},
|
||||
"xml2js": {
|
||||
"0.1.13": "438ff3b1d85a51ad659ffc2ebe83403e10c98722"
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"0.4.2": "1776d65f3fdbad470a08d8604cdeb1c4e540ff83"
|
||||
},
|
||||
"xmldom": {
|
||||
"0.1.16": "cf2602832b1ab5c3e6813fca08fe70196ba15e8c"
|
||||
},
|
||||
"yamlparser": {
|
||||
"0.0.2": "32393e6afc70c8ca066b6650ac6738b481678ebc"
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
# About
|
||||
|
||||
This is a fake LDAP server that makes it convenient to test the LDAP connection and
|
||||
binding functionality without spinning up and populating a real ldap server.
|
||||
|
||||
## Usage
|
||||
|
||||
> node mock-ldap-server/server.js
|
||||
|
||||
This will start the server and have it listen on the default LDAP port: 1389
|
|
@ -1,197 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const util = require("util"),
|
||||
path = require("path"),
|
||||
express = require('express');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This provides *TWO*, yes *TWO* servers.
|
||||
*
|
||||
* 1. the Mock LDAP server that provides some basic
|
||||
* users for testing
|
||||
*
|
||||
* 2. a HTTP server that provides an interface for changing
|
||||
* LDAP data and various other tweaks to make Q/A easier
|
||||
*/
|
||||
|
||||
var ldap = require("ldapjs");
|
||||
var createLdapMock = require("../server/lib/ldapMock");
|
||||
var ldapMock = createLdapMock();
|
||||
|
||||
|
||||
var fakeLatency = 1;
|
||||
var LDAP_LISTENING = false;
|
||||
var ldapServer = null;
|
||||
|
||||
function resetServer() {
|
||||
|
||||
if (ldapServer && ldapServer.close) {
|
||||
try { ldapServer.close(); } catch (e) {}
|
||||
}
|
||||
|
||||
ldapServer = ldap.createServer();
|
||||
ldapServer.bind('dc=mozilla', function(req, res, next) {
|
||||
if (fakeLatency == -1 ) return;
|
||||
|
||||
setTimeout(function() {
|
||||
ldapMock.bindHandler.call(ldapServer, req, res, next);
|
||||
}, fakeLatency);
|
||||
});
|
||||
|
||||
ldapServer.search('dc=mozilla', function(req, res, next) {
|
||||
if (fakeLatency == -1 ) return;
|
||||
|
||||
setTimeout(function() {
|
||||
ldapMock.searchHandler.call(ldapServer, req, res, next);
|
||||
}, fakeLatency);
|
||||
});
|
||||
|
||||
ldapServer.on('bind', function(bindEvent) {
|
||||
console.log(util.format("Bind Event: Success - %s, dn: %s, credentials: %s",
|
||||
bindEvent.success, bindEvent.dn, bindEvent.credentials));
|
||||
});
|
||||
|
||||
ldapServer.on('authorize', function(e) {
|
||||
console.log(util.format("Auth %s, dn: %s", (e.success) ? "OK" : "FAIL", e.dn));
|
||||
});
|
||||
|
||||
|
||||
ldapServer.on('listening', function() {
|
||||
console.log("LDAP SERVER LISTENING on port: " + ldapServer.port);
|
||||
});
|
||||
|
||||
ldapServer.on('close', function() {
|
||||
LDAP_LISTENING = false;
|
||||
console.log("LDAP SERVER STOPPED LISTENING");
|
||||
});
|
||||
}
|
||||
|
||||
function startLDAP() {
|
||||
if (LDAP_LISTENING === true) {
|
||||
return;
|
||||
}
|
||||
LDAP_LISTENING = true;
|
||||
ldapServer.listen(1389, function() {
|
||||
ldapServer.emit("listening");
|
||||
});
|
||||
}
|
||||
|
||||
resetServer();
|
||||
startLDAP();
|
||||
|
||||
/**
|
||||
* HTTP Back-channel server for mucking w/ things ;)
|
||||
*/
|
||||
if (!process.env.QAUser || !process.env.QAPass) {
|
||||
console.log("NO QAUser/QAPass set");
|
||||
process.exit(1)
|
||||
} else {
|
||||
var QAUser = process.env.QAUser,
|
||||
QAPass = process.env.QAPass;
|
||||
}
|
||||
app = express.createServer();
|
||||
app.use(express.basicAuth(QAUser, QAPass));
|
||||
app.use(express.bodyParser());
|
||||
app.configure(function() {
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'ejs');
|
||||
});
|
||||
|
||||
app.get('/', function(req, res, next) {
|
||||
res.render("main", {
|
||||
latency: fakeLatency,
|
||||
state: LDAP_LISTENING,
|
||||
directory: ldapMock.directory
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/reset', function(req, res, next) {
|
||||
fakeLatency = 1;
|
||||
ldapMock = createLdapMock();
|
||||
resetServer();
|
||||
startLDAP();
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// Updates the LDAP database of users
|
||||
app.post('/update-users', function(req, res, next) {
|
||||
var dir = ldapMock.directory;
|
||||
for (var email in req.body) {
|
||||
for(var i=0; i<dir.length;i++) {
|
||||
if (dir[i].attributes.mail == email) {
|
||||
dir[i].attributes.password = req.body[email];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// Changes how long the LDAP server will wait to respond
|
||||
app.post('/set-latency', function(req, res, next) {
|
||||
fakeLatency = parseInt(req.body.latency, "10");
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// Sets LDAP state up/down
|
||||
app.post('/set-state', function(req, res, next) {
|
||||
if (req.body.state == "up") {
|
||||
startLDAP();
|
||||
} else {
|
||||
ldapServer.close();
|
||||
}
|
||||
|
||||
res.redirect('/');
|
||||
|
||||
});
|
||||
|
||||
// resets everything back to defaults
|
||||
app.post('/reset-all', function(req, res, next) {
|
||||
|
||||
});
|
||||
|
||||
app.listen('3001', function(err) {
|
||||
if (err) {
|
||||
console.error("ERROR", err);
|
||||
} else {
|
||||
console.log("Web Server running on 0.0.0.0:3001");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
/* Example queries you can run!
|
||||
|
||||
Get all emails
|
||||
---------------------
|
||||
> ldapsearch -H ldap://localhost:1389 -x \
|
||||
-D mail=user@mozilla.com,o=com,dc=mozilla -w testtest \
|
||||
-LLL -b "dc=mozilla, o=com" mail=*
|
||||
|
||||
Search by email
|
||||
---------------
|
||||
> ldapsearch -H ldap://localhost:1389 -x \
|
||||
-D mail=user@mozilla.com,o=com,dc=mozilla -w testtest \
|
||||
-LLL -b "dc=mozilla, o=com" mail=user@mozilla.org
|
||||
|
||||
Bind wrong w/ invalid user
|
||||
--------------------------
|
||||
> ldapsearch -H ldap://localhost:1389 -x \
|
||||
-D cn=not_here,o=com,dc=mozilla -w testtest \
|
||||
-LLL -b "dc=mozilla, o=com" mail=user@mozilla.org
|
||||
|
||||
Bind wrong w/ bad password
|
||||
--------------------------
|
||||
> ldapsearch -H ldap://localhost:1389 -x \
|
||||
-D cn=vinz,o=com,dc=mozilla -w OOPS \
|
||||
-LLL -b "dc=mozilla, o=com" mail=user@mozilla.org
|
||||
|
||||
Don't bind at all
|
||||
--------------------------
|
||||
> ldapsearch -H ldap://localhost:1389 -x \
|
||||
-LLL -b "dc=mozilla, o=com" mail=*
|
||||
|
||||
*/
|
|
@ -1,14 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>LDAP Debugger</title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
</head>
|
||||
<body>
|
||||
<div id="content">
|
||||
<%- body %>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,64 +0,0 @@
|
|||
<h1>LDAP Server Q/A Tool</h1>
|
||||
|
||||
<h2>RESET!</h2>
|
||||
|
||||
<form method="post" action="/reset">
|
||||
<input type="submit" value="RESET BACK TO DEFAULTS">
|
||||
</form>
|
||||
|
||||
<h2>Server Status</h2>
|
||||
|
||||
<form method="POST" action="/set-state">
|
||||
<p>Current State: <span><%=((state==true)? "UP" : "DOWN")%></span></p>
|
||||
|
||||
<input type="radio" name="state" value="up" id="state_up"/>
|
||||
<label for="state_up">UP</label>
|
||||
<br/>
|
||||
|
||||
<input type="radio" name="state" value="down" id="state_down"/>
|
||||
<label for="state_down">DOWN</label>
|
||||
<br/>
|
||||
|
||||
<input type="submit" value="Change Server State">
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>User Editor</h2>
|
||||
<form method="POST" action="/update-users">
|
||||
<table border="1" cellpadding="2" cellspacing="0">
|
||||
<tr>
|
||||
<th>Email Address</th>
|
||||
<th>Password</th>
|
||||
</tr>
|
||||
<% for(var i=1; i<directory.length; i++) { %>
|
||||
<% if(directory[i].attributes.mail.indexOf("mozilla") == -1) { continue;} %>
|
||||
<tr>
|
||||
<td><%=directory[i].attributes.mail%></td>
|
||||
<td><input type="text" name="<%=directory[i].attributes.mail%>" value="<%=directory[i].attributes.password%>"></td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</table>
|
||||
<br/>
|
||||
<input type="submit" value="Update Users">
|
||||
</form>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Response Latency</h2>
|
||||
|
||||
<form method="POST" action="/set-latency">
|
||||
<p>Current Latency: <span><%=((latency == -1)? "Blackhole" : latency + "ms")%></span></p>
|
||||
|
||||
<select name="latency">
|
||||
<option value="1">1 ms</option>
|
||||
<option value="30">30 ms</option>
|
||||
<option value="1000">1 second</option>
|
||||
<option value="5000">5 seconds</option>
|
||||
<option value="10000">10 seconds</option>
|
||||
<option value="-1">Blackhole</option>
|
||||
</select>
|
||||
<input type="submit" value="Set Latency"/>
|
||||
</form>
|
||||
|
||||
|
53
package.json
|
@ -1,53 +0,0 @@
|
|||
{
|
||||
"author": "Ozten <shout@ozten.com> (http://ozten.com)",
|
||||
"name": "clortho",
|
||||
"description": "A server that provides Persona support for mozilla.{com,org} emails.",
|
||||
"version": "0.0.0",
|
||||
"licenses" : [{
|
||||
"type": "MPL 2.0",
|
||||
"url": "https://mozilla.org/MPL/2.0/"
|
||||
}],
|
||||
"homepage": "https://github.com/mozilla/vinz-clortho",
|
||||
"bugs": "https://github.com/mozilla/vinz-clortho/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mozilla/vinz-clortho"
|
||||
},
|
||||
"main": "clortho",
|
||||
"engines": {
|
||||
"node": "~0.6.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"client-sessions": "0.3.0",
|
||||
"convict": "0.1.0",
|
||||
"ejs": "0.6.1",
|
||||
"express": "2.5.11",
|
||||
"i18n-abide": "0.0.11",
|
||||
"jwcrypto": "0.4.3",
|
||||
"ldapjs": "https://github.com/lloyd/node-ldapjs/archive/ephemeral_port.tar.gz",
|
||||
"node-statsd": "https://github.com/downloads/lloyd/node-statsd/0509f85.tgz",
|
||||
"winston": "0.7.1",
|
||||
"cef": "0.3.3",
|
||||
"connect-fonts": "0.0.9",
|
||||
"connect-fonts-opensans": "0.0.4",
|
||||
"lockdown": "0.0.5",
|
||||
"validator": "1.1.3",
|
||||
"underscore": "1.4.4",
|
||||
"useragent": "2.0.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"awsbox": "0.4.5",
|
||||
"mocha": "1.9.0",
|
||||
"should": "1.2.2",
|
||||
"request": "2.16.6",
|
||||
"jshint": "2.1.2",
|
||||
"walk": "2.2.1",
|
||||
"temp": "0.5.1",
|
||||
"optimist": "0.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "LOCAL_DEV=true node ./server/bin/clortho",
|
||||
"test": "LOCAL_DEV=true mocha -R spec tests/",
|
||||
"preinstall": "node ./scripts/lockdown.js"
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
#
|
||||
# so all of our tags are in a consistent naming format
|
||||
#
|
||||
#
|
||||
# Make sure everything is clean
|
||||
#
|
||||
|
||||
cd $(dirname $0)/../
|
||||
BASE=$PWD
|
||||
|
||||
# Make sure there are no uncommited changes
|
||||
# src: http://stackoverflow.com/a/3879077/445792
|
||||
git update-index -q --ignore-submodules --refresh
|
||||
err=0
|
||||
|
||||
# Disallow unstaged changes in the working tree
|
||||
if ! git diff-files --quiet --ignore-submodules --
|
||||
then
|
||||
echo >&2 "cannot $1: you have unstaged changes."
|
||||
git diff-files --name-status -r --ignore-submodules -- >&2
|
||||
err=1
|
||||
fi
|
||||
|
||||
# Disallow uncommitted changes in the index
|
||||
if ! git diff-index --cached --quiet HEAD --ignore-submodules --
|
||||
then
|
||||
echo >&2 "cannot $1: your index contains uncommitted changes."
|
||||
git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
|
||||
err=1
|
||||
fi
|
||||
|
||||
if [ $err = 1 ]
|
||||
then
|
||||
echo >&2 "Please commit or stash them."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RDATE=$(date '+%Y_%m_%d.%H.%M.%S')
|
||||
TAG="rel$RDATE"
|
||||
LAST_RELEASE=$(git tag --list | grep ^rel | sort -r | head -1)
|
||||
|
||||
if [ ! -e $BASE/ChangeLog ]; then
|
||||
touch $BASE/ChangeLog
|
||||
fi
|
||||
|
||||
if [ -z "$LAST_RELEASE" ]; then
|
||||
echo "Error: No last release found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z $(git log --pretty=format:%s "$LAST_RELEASE..HEAD") ]; then
|
||||
echo "Abort: No changes since $LAST_RELEASE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TMPFILE=$(mktemp /tmp/idpchangelog.XXXXX)
|
||||
echo "$TAG:" > $TMPFILE
|
||||
echo >> $TMPFILE
|
||||
git log --no-merges --pretty=" * %h %s" "$LAST_RELEASE..HEAD" >> $TMPFILE
|
||||
|
||||
echo >> $TMPFILE
|
||||
cat $BASE/ChangeLog >> $TMPFILE
|
||||
cat $TMPFILE
|
||||
cat $TMPFILE > $BASE/ChangeLog
|
||||
git add $BASE/ChangeLog
|
||||
git commit -am "Create Release: $TAG"
|
||||
git tag $TAG
|
|
@ -1,62 +0,0 @@
|
|||
#!/bin/sh
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# About:
|
||||
#
|
||||
# This script generates a SRPM of the source + rpm spec file to be passed
|
||||
# the mock RPM build tool. See: http://fedoraproject.org/wiki/Projects/Mock
|
||||
#
|
||||
|
||||
set -e
|
||||
|
||||
progname=$(basename $0)
|
||||
TOP="$(cd $(dirname $0)/..; pwd)" # top level of the checkout
|
||||
cd $TOP
|
||||
|
||||
if [ $# -ne 1 ]; then
|
||||
echo "Usage: $(basename $0) (GIT_SHA | GIT_TAG | GIT_BRANCH)"
|
||||
exit 1
|
||||
else
|
||||
VER=$1
|
||||
fi
|
||||
|
||||
rm -rf rpmbuild
|
||||
mkdir -p rpmbuild/SRPM rpmbuild/SOURCES rpmbuild/SPECS rpmbuild/BUILD rpmbuild/TMP
|
||||
git fetch
|
||||
git clone . rpmbuild/TMP &>/dev/null
|
||||
cd rpmbuild/TMP
|
||||
git checkout $VER &>/dev/null
|
||||
|
||||
export GIT_REVISION=$(git log -1 --oneline)
|
||||
export GIT_HASH=$(echo $GIT_REVISION | cut -d ' ' -f1)
|
||||
export MOZIDP_VER="$(echo $VER | sed 's/-/_/g').$GIT_HASH"
|
||||
|
||||
# build a tarball so we can create the source RPM correctly
|
||||
tar --exclude .git \
|
||||
-czf "$TOP/rpmbuild/SOURCES/mozidp-$MOZIDP_VER.tar.gz" .
|
||||
|
||||
cd $TOP
|
||||
set +e
|
||||
|
||||
# generate a new spec file with the version baked in
|
||||
TMPFILE=$TOP/rpmbuild/SPECS/mozidp.spec
|
||||
sed "s/__VERSION__/$MOZIDP_VER/g" scripts/mozidp.spec.template > $TMPFILE
|
||||
|
||||
echo "Building Source RPM"
|
||||
mock --root epel-6-x86_64 \
|
||||
--buildsrpm \
|
||||
--spec $TMPFILE \
|
||||
--sources $TOP/rpmbuild/SOURCES
|
||||
|
||||
FILENAME=mozilla-idp-server-${MOZIDP_VER}-1.el6.src.rpm
|
||||
SRPM_SOURCE=/var/lib/mock/epel-6-x86_64/result/$FILENAME
|
||||
|
||||
if [ ! -e $SRPM_SOURCE ]; then
|
||||
echo "Failed building SRPM" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv /var/lib/mock/epel-6-x86_64/result/$FILENAME $TOP/rpmbuild/SRPM/
|
||||
echo "Wrote: $TOP/rpmbuild/SRPM/$FILENAME"
|
|
@ -1,58 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* scripts/gen_keys.js creates public and private keys suitable for
|
||||
key signing Persona Primary IdP's.
|
||||
|
||||
Usage:
|
||||
scripts/gen_keys.js
|
||||
|
||||
Will create these files
|
||||
|
||||
server/config/public-key.json
|
||||
server/config/secret-key.json
|
||||
|
||||
If these files already exist, this script will show an error message
|
||||
and exit. You must remove both keys if you want to generate a new
|
||||
keypair.
|
||||
*/
|
||||
const jwcrypto = require("jwcrypto")
|
||||
, fs = require('fs')
|
||||
, assert = require("assert")
|
||||
, configDir = fs.realpathSync(__dirname + "/../server/config")
|
||||
, pubKeyFile = configDir + "/public-key.json"
|
||||
, secretKeyFile = configDir + "/secret-key.json" ;
|
||||
|
||||
require("jwcrypto/lib/algs/rs");
|
||||
|
||||
try {
|
||||
assert(fs.existsSync(configDir), "Config dir" + configDir + " not found");
|
||||
assert(! fs.existsSync(pubKeyFile), "public key file: ["+pubKeyFile+"] already exists");
|
||||
assert(! fs.existsSync(secretKeyFile), "public key file: ["+secretKeyFile+"] already exists");
|
||||
} catch(e) {
|
||||
console.error("Error: " + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("Generating keypair. (install libgmp if this takes more than a second)");
|
||||
|
||||
// wondering about `keysize: 256`?
|
||||
// well, 257 = 2048bit key
|
||||
// still confused? see: https://github.com/mozilla/jwcrypto/blob/master/lib/algs/ds.js#L37-L57
|
||||
jwcrypto.generateKeypair(
|
||||
{algorithm: 'RS', keysize: 256},
|
||||
function(err, keypair) {
|
||||
|
||||
var pubKey = keypair.publicKey.serialize()
|
||||
var secretKey = keypair.secretKey.serialize()
|
||||
|
||||
|
||||
fs.writeFileSync(pubKeyFile, pubKey);
|
||||
console.log("Public Key saved:", pubKeyFile);
|
||||
|
||||
fs.writeFileSync(secretKeyFile, secretKey);
|
||||
console.log("Secret Key saved:", pubKeyFile);
|
||||
}
|
||||
);
|
|
@ -1,192 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
if (process.env['NPM_LOCKDOWN_RUNNING']) process.exit(0);
|
||||
|
||||
console.log("NPM Lockdown is here to check your dependencies! Never fear!");
|
||||
|
||||
var http = require('http'),
|
||||
crypto = require('crypto'),
|
||||
exec = require('child_process').exec,
|
||||
fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
try {
|
||||
var lockdownJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'lockdown.json')));
|
||||
} catch(e) {
|
||||
console.log("\nERROR: I cannot read lockdown.json! run node_modules/.bin/lockdown-relock to generate!\n");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var boundPort;
|
||||
|
||||
// during execution fatal errors will be appended to this list
|
||||
var errors = [];
|
||||
|
||||
// during execution non-fatal warnings will be appended to this list
|
||||
var warn = [];
|
||||
|
||||
function rewriteURL(u) {
|
||||
return u.replace('registry.npmjs.org', '127.0.0.1:' + boundPort);
|
||||
}
|
||||
|
||||
function packageOk(name, ver, sha, required) {
|
||||
if (!lockdownJson[name]) {
|
||||
if (required) {
|
||||
errors.push("package '" + name + "' not in lockdown.json!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lockdownJson[name][ver] === undefined) {
|
||||
if (required) {
|
||||
errors.push("package version " + name + "@" + ver + " not in lockdown.json!");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// a '*' shasum is not checked
|
||||
var wantSHA = lockdownJson[name][ver];
|
||||
if (wantSHA !== '*' && wantSHA !== sha) {
|
||||
if (required) {
|
||||
errors.push("package " + name + "@" + ver + " has a different checksum (" +
|
||||
wantSHA + " v. " + sha + ")");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (wantSHA === '*') {
|
||||
warn.push("Lockdown cannot guarantee your saftey! No sha for pkg " + name + "@" + ver +
|
||||
" in lockdown.json");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
function rewriteVersionMD(json) {
|
||||
if (typeof json === 'string') json = JSON.parse(json);
|
||||
if (!json.error) {
|
||||
json.dist.tarball = rewriteURL(json.dist.tarball);
|
||||
|
||||
// is the name/version/sha in our lockdown.json?
|
||||
if (!packageOk(json.name, json.version, json.dist.shasum, true)) return null;
|
||||
}
|
||||
return JSON.stringify(json);
|
||||
}
|
||||
|
||||
function rewritePackageMD(json) {
|
||||
if (typeof json === 'string') json = JSON.parse(json);
|
||||
if (!json.error) {
|
||||
Object.keys(json.versions).forEach(function(ver) {
|
||||
var data = json.versions[ver];
|
||||
var name = data.name;
|
||||
var sha = data.dist ? data.dist.shasum : undefined;
|
||||
|
||||
if (packageOk(name, ver, sha, false)) {
|
||||
data.dist.tarball = rewriteURL(data.dist.tarball);
|
||||
} else {
|
||||
delete json.versions[ver];
|
||||
}
|
||||
});
|
||||
}
|
||||
return JSON.stringify(json);
|
||||
}
|
||||
|
||||
var server = http.createServer(function (req, res) {
|
||||
if (req.method !== 'GET') {
|
||||
return res.end('non GET requests not supported', 501);
|
||||
}
|
||||
|
||||
// what type of request is this?
|
||||
// 1. specific version json metadata (when explicit dependency is expressed)
|
||||
// - for these requests we should verify the name/version/sha advertised is allowed
|
||||
// 2. package version json metadata (when version range is expressed - including '*')
|
||||
// XXX: for these requests we should prune all versions that are not allowed
|
||||
// 3. tarball - actual bits
|
||||
// XXX: for these requests we should verify the name/version/sha matches something
|
||||
// allowed, otherwise block the transaction
|
||||
var arr = req.url.substr(1).split('/');
|
||||
var type = [ '', 'package_metadata', 'version_metadata', 'tarball' ][arr.length];
|
||||
|
||||
// let's extract pkg name and version sensitive to the type of request being performed.
|
||||
var pkgname, pkgver;
|
||||
if (type === 'tarball') {
|
||||
pkgname = arr[0];
|
||||
var getVer = new RegExp("^" + pkgname + "-(.*)\\.tgz$");
|
||||
pkgver = getVer.exec(arr[2])[1];
|
||||
} else if (type === 'version_metadata') {
|
||||
pkgname = arr[0];
|
||||
pkgver = arr[1];
|
||||
} else if (type === 'package_metadata') {
|
||||
pkgname = arr[0];
|
||||
}
|
||||
|
||||
var hash = crypto.createHash('sha1');
|
||||
|
||||
var r = http.request({
|
||||
host: 'registry.npmjs.org',
|
||||
port: 80,
|
||||
method: req.method,
|
||||
path: req.url,
|
||||
agent: false
|
||||
}, function(rres) {
|
||||
res.setHeader('Content-Type', rres.headers['content-type']);
|
||||
if (type === 'tarball') res.setHeader('Content-Length', rres.headers['content-length']);
|
||||
var b = "";
|
||||
rres.on('data', function(d) {
|
||||
hash.update(d);
|
||||
if (type != 'tarball') b += d;
|
||||
else res.write(d);
|
||||
});
|
||||
rres.on('end', function() {
|
||||
if (type === 'tarball') {
|
||||
res.end();
|
||||
} else {
|
||||
if (type === 'package_metadata') {
|
||||
b = rewritePackageMD(b);
|
||||
} else if (type === 'version_metadata') {
|
||||
b = rewriteVersionMD(b);
|
||||
}
|
||||
if (b === null) {
|
||||
res.writeHead(404);
|
||||
res.end("package installation disallowed by lockdown");
|
||||
} else {
|
||||
res.setHeader('Content-Length', Buffer.byteLength(b));
|
||||
res.writeHead(rres.statusCode);
|
||||
res.end(b);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
r.end();
|
||||
});
|
||||
|
||||
server.listen(process.env['LOCKDOWN_PORT'] || 0, '127.0.0.1', function() {
|
||||
boundPort = server.address().port;
|
||||
|
||||
var child = exec('npm install', {
|
||||
env: {
|
||||
NPM_CONFIG_REGISTRY: 'http://127.0.0.1:' + boundPort,
|
||||
NPM_LOCKDOWN_RUNNING: "true",
|
||||
PATH: process.env['PATH'],
|
||||
HOME: process.env['HOME']
|
||||
},
|
||||
cwd: process.cwd()
|
||||
}, function(e) {
|
||||
if (warn.length) {
|
||||
console.log();
|
||||
console.log("LOCKDOWN WARNINGS:");
|
||||
warn.forEach(function(e) { console.log(" ", e); });
|
||||
console.log();
|
||||
}
|
||||
if (errors.length) {
|
||||
console.log();
|
||||
console.log("LOCKDOWN ERRORS:");
|
||||
errors.forEach(function(e) { console.log(" ", e); });
|
||||
console.log();
|
||||
}
|
||||
process.exit(e ? 1 : 0);
|
||||
});
|
||||
child.stdout.pipe(process.stdout);
|
||||
child.stderr.pipe(process.stderr);
|
||||
});
|
|
@ -1,42 +0,0 @@
|
|||
%define _rootdir /opt/mozilla-idp-server
|
||||
%define version __VERSION__
|
||||
|
||||
Name: mozilla-idp-server
|
||||
Version: %{version}
|
||||
Release: 1%{?dist}
|
||||
Summary: Mozilla IdP Server
|
||||
Packager: Benson Wong <bwong@mozilla.com>
|
||||
Group: Development/Libraries
|
||||
License: MPL 2.0
|
||||
URL: https://github.com/mozilla/vinz-clortho
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
|
||||
AutoReqProv: no
|
||||
Requires: openssl, nodejs == 0.8.24, gmp-devel
|
||||
BuildRequires: gmp-devel, gcc-c++, git, make, npm, nodejs == 0.8.24
|
||||
Source: mozidp-%{version}.tar.gz
|
||||
|
||||
%description
|
||||
Mozilla IdP Server
|
||||
|
||||
%prep
|
||||
%setup -q -c
|
||||
|
||||
%build
|
||||
npm install
|
||||
export PATH=$PWD/node_modules/.bin:$PATH
|
||||
echo "$GIT_REVISION" > static/ver.txt
|
||||
|
||||
%install
|
||||
rm -rf %{buildroot}
|
||||
mkdir -p %{buildroot}%{_rootdir}
|
||||
for f in node_modules docs mock-ldap-server scripts server static tests *.json *.md; do
|
||||
cp -rp $f %{buildroot}%{_rootdir}/
|
||||
done
|
||||
mkdir -p %{buildroot}%{_rootdir}/config
|
||||
|
||||
%clean
|
||||
rm -rf %{buildroot}
|
||||
|
||||
%files
|
||||
%defattr(-,root,root,-)
|
||||
%{_rootdir}
|
|
@ -1,75 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
console.error('uncaught exception', err);
|
||||
});
|
||||
|
||||
const ldap = require('ldapjs'),
|
||||
config = require('../server/lib/configuration'),
|
||||
path = require('path');
|
||||
|
||||
var argv = require('optimist')
|
||||
.usage('Test authentication against LDAP.\nUsage: $0')
|
||||
.alias('h', 'help')
|
||||
.describe('h', 'display this usage message')
|
||||
.alias('u', 'url')
|
||||
.describe('u', 'LDAP server url')
|
||||
.default('u', 'ldaps://addressbook.mozilla.com:636')
|
||||
.alias('a', 'address')
|
||||
.describe('a', 'email address to authenticate (may be an alias)')
|
||||
.demand('a')
|
||||
.alias('p', 'password')
|
||||
.describe('p', 'LDAP account password')
|
||||
.demand('p')
|
||||
.alias('c', 'canonical')
|
||||
.describe('c', 'canonical LDAP password (required when email is an alias)');
|
||||
|
||||
var args = argv.argv;
|
||||
|
||||
// request context (cookie jar, etc)
|
||||
var ctx = {};
|
||||
|
||||
if (args.h) {
|
||||
argv.showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
var auth = require('../server/lib/auth');
|
||||
|
||||
var dn = auth.convertEmailToDN(args.c || args.a);
|
||||
|
||||
auth.canonicalAddress({
|
||||
email: args.a,
|
||||
dn: dn,
|
||||
pass: args.p,
|
||||
url: args.u
|
||||
}, function(err, canonicalAddress) {
|
||||
if (err) {
|
||||
console.log(util.format("communication with LDAP failed (%s): %s",
|
||||
err.name, err.message));
|
||||
} else {
|
||||
if (canonicalAddress == args.a) {
|
||||
console.log(args.a, "is canonical");
|
||||
} else {
|
||||
console.log(util.format("canonical address for %s is %s",
|
||||
args.a, canonicalAddress));
|
||||
}
|
||||
|
||||
auth.authUser({
|
||||
url: args.u,
|
||||
email: canonicalAddress,
|
||||
pass: args.p
|
||||
}, function (err, passed) {
|
||||
if (err) {
|
||||
console.log(util.format("authentication with LDAP failed (%s): %s",
|
||||
err.name, err.message));
|
||||
} else {
|
||||
console.log(passed);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
|
@ -1,179 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const path = require('path');
|
||||
|
||||
// ./server is our current working directory
|
||||
process.chdir(path.dirname(__dirname));
|
||||
|
||||
const config = require('../lib/configuration'),
|
||||
crypto = require('crypto'),
|
||||
express = require('express'),
|
||||
fs = require('fs'),
|
||||
http = require('http'),
|
||||
i18n = require('i18n-abide'),
|
||||
p3p = require('../lib/p3p'),
|
||||
routes = require('../routes').routes(),
|
||||
sessions = require('client-sessions'),
|
||||
statsd = require('../lib/statsd'),
|
||||
util = require('util'),
|
||||
logger = require('../lib/logging').logger,
|
||||
font_middleware = require("connect-fonts"),
|
||||
opensans = require("connect-fonts-opensans"),
|
||||
validate = require('../lib/validate');
|
||||
|
||||
// log uncaught exceptions
|
||||
process.on('uncaughtException', function(err) {
|
||||
if (statsd) statsd.increment('server.uncaught_exception');
|
||||
logger.error(err.stack || err);
|
||||
});
|
||||
|
||||
var app;
|
||||
|
||||
app = express.createServer();
|
||||
|
||||
app.use(express.errorHandler());
|
||||
|
||||
app.set('views', path.join(process.cwd(), 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
// send web log messages to our logging abstraction
|
||||
app.use(express.logger({
|
||||
stream: {
|
||||
write: function(message){
|
||||
// trim newlines as our logger inserts them for us.
|
||||
if (typeof message === 'string') message = message.trim();
|
||||
logger.info(message);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
app.use(express.bodyParser());
|
||||
app.use(express.methodOverride());
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
// We will always be deployed behind an SSL terminator. This
|
||||
// flag tells client-sessions that it's ok to send cookies which
|
||||
// will only be returned over SSL.
|
||||
req.connection.proxySecure = true;
|
||||
if (!config.get('local_development')) {
|
||||
// when we're not running local development, append STS headers to
|
||||
// all requests to force browsers to use SSL
|
||||
|
||||
// expires in 30 days
|
||||
res.setHeader("Strict-Transport-Security", "max-age=10886400; includeSubdomains");
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
if (config.get('local_development')) {
|
||||
logger.warn("Running with insecure cookies, not appropriate for production");
|
||||
}
|
||||
|
||||
app.use(sessions({
|
||||
cookieName: 'session',
|
||||
secret: config.get('cookie.secret'),
|
||||
duration: config.get('cookie.duration_ms'),
|
||||
cookie: {
|
||||
maxAge: config.get('cookie.duration_ms'),
|
||||
httpOnly: true,
|
||||
// only allow non-secure cookies during local development
|
||||
secure: !config.get('local_development')
|
||||
}
|
||||
}));
|
||||
|
||||
// add p3p cookie support
|
||||
app.use(p3p);
|
||||
|
||||
app.use(font_middleware.setup({
|
||||
fonts: [ opensans ],
|
||||
ua: "all",
|
||||
maxage: 180 * 24 * 60 * 60 * 1000, // 180 days
|
||||
compress: true,
|
||||
allow_origin: "*"
|
||||
}));
|
||||
|
||||
app.use(express.csrf());
|
||||
|
||||
app.use(i18n.abide({
|
||||
supported_languages: config.get('supported_languages'),
|
||||
default_lang: config.get('default_lang'),
|
||||
debug_lang: config.get('debug_lang'),
|
||||
locale_directory: config.get('locale_directory')
|
||||
}));
|
||||
|
||||
// set up local variables available to templates
|
||||
app.use(function(req, res, next) {
|
||||
// delete the supplied CSRF token at this point, it's already been checked and
|
||||
// subsequent API endpoints perform rigorous input validation.
|
||||
if (req.body && req.body._csrf) delete req.body._csrf;
|
||||
res.local('csrf_token', req.session._csrf);
|
||||
res.local('browserid_server', config.get('browserid_server'));
|
||||
var s = req.session || null;
|
||||
res.local('session', s);
|
||||
next();
|
||||
});
|
||||
|
||||
// all API requests should be uncachable
|
||||
app.use(function(req, res, next) {
|
||||
if (/^\/api\//.test(req.url)) {
|
||||
res.setHeader('Cache-Control', 'no-cache, max-age=0');
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// documents
|
||||
app.get('/.well-known/browserid', routes.well_known_browserid);
|
||||
app.get('/', routes.welcome);
|
||||
app.get('/sign_in', routes.signin);
|
||||
app.get('/provision', routes.provision);
|
||||
|
||||
// monitoring
|
||||
app.get('/node_status', routes.elb_check);
|
||||
app.get('/ldap_status', routes.checkStatus);
|
||||
|
||||
// APIs
|
||||
app.get('/api/session_context', routes.session_context);
|
||||
app.post('/api/sign_in', validate({
|
||||
user: 'email',
|
||||
pass: 'password'
|
||||
}), routes.check_signin);
|
||||
app.post('/api/provision', validate({
|
||||
user: 'email',
|
||||
pubkey: 'pubkey'
|
||||
}), routes.provision_key);
|
||||
|
||||
// for testing
|
||||
app.get('/signout', routes.signout);
|
||||
|
||||
// static files
|
||||
app.use(express.static(path.join(process.cwd(), '..', 'static')));
|
||||
app.use(routes.handle404);
|
||||
|
||||
var dntPolicy = fs.readFileSync(path.join(process.cwd(), '..', 'static', 'dnt-policy.txt')).toString();
|
||||
app.get('/.well-known/dnt-policy.txt', function(req, res) {
|
||||
res.setHeader('Content-Type', 'text/plain; charset=utf8');
|
||||
res.send(dntPolicy);
|
||||
});
|
||||
|
||||
function startup(cb) {
|
||||
app.listen(config.get('http_port'), config.get('http_address'), function(err) {
|
||||
statsd.increment('server.started');
|
||||
if (cb) cb(err, app.address());
|
||||
});
|
||||
}
|
||||
|
||||
// allow clortho to be invoked from the command line or as a library
|
||||
// ref: http://stackoverflow.com/a/6398335/445792
|
||||
if (require.main === module) {
|
||||
// command line invocation
|
||||
startup(function(err, address) {
|
||||
logger.info(util.format("running on http://%s:%s",
|
||||
address.address, address.port));
|
||||
});
|
||||
} else {
|
||||
exports.startup = startup;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"basic_auth_realm": "Basic realm=\"Mozilla Corporation - LDAP Login\"",
|
||||
"browserid_server": "http://127.0.0.1:10002",
|
||||
"cookie": {
|
||||
"secret": "YOU MUST CHANGE ME",
|
||||
"duration": 86400000
|
||||
},
|
||||
"default_lang": "en-US",
|
||||
"debug_lang": "it-CH",
|
||||
"http_port": 3000,
|
||||
"issuer": "dev.clortho.mozilla.org",
|
||||
"ldap_bind_dn": "mail=USERNAME@mozilla.com,o=com,dc=mozilla",
|
||||
"ldap_bind_password": "password",
|
||||
"ldap_server_url": "ldaps://addressbook.mozilla.com:636",
|
||||
"locale_directory": "locale",
|
||||
"signin_method": "basicauth",
|
||||
"supported_languages": ["en-US"],
|
||||
"use_https": true
|
||||
}
|
|
@ -1,328 +0,0 @@
|
|||
// vim: shiftwidth=2
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const ldap = require('ldapjs'),
|
||||
config = require('./configuration'),
|
||||
logger = require('./logging').logger,
|
||||
statsd = require('../lib/statsd'),
|
||||
util = require('util'),
|
||||
_ = require('underscore'),
|
||||
fs = require('fs');
|
||||
|
||||
// check required configuration at startup
|
||||
[ 'ldap_server_url', 'ldap_bind_dn', 'ldap_bind_password' ].forEach(function(k) {
|
||||
if (!config.has(k)) {
|
||||
logger.error(util.format("Configuration error, you must specifiy '%s'", k));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// create and connect an LDAP client, populate the options block
|
||||
function createClient(opts, cb) {
|
||||
var connectStartTime = new Date();
|
||||
|
||||
if (typeof opts !== 'object' || opts === null) throw new Error('invalid options parameter');
|
||||
opts.url = opts.url || config.get('ldap_server_url');
|
||||
opts.errorCallback = opts.errorCallback || function(err) {
|
||||
logger.warn(util.format('LDAP connection ended with unhandled error: %s', err));
|
||||
};
|
||||
|
||||
cb = _.once(cb);
|
||||
|
||||
var client = ldap.createClient({
|
||||
url: opts.url,
|
||||
connectTimeout: opts.connectTimeout || config.get('ldap_server_connect_timeout')
|
||||
});
|
||||
|
||||
var connected = false;
|
||||
|
||||
client.on('close', function(err) {
|
||||
if (!connected) {
|
||||
err = "connect failed";
|
||||
}
|
||||
if (err) {
|
||||
if (opts.errorCallback) opts.errorCallback(err);
|
||||
opts.errorCallback = null;
|
||||
}
|
||||
});
|
||||
|
||||
client.on('error', function(err) {
|
||||
if (opts.errorCallback) {
|
||||
opts.errorCallback(err);
|
||||
}
|
||||
opts.errorCallback = null;
|
||||
});
|
||||
|
||||
client.on('connect', function() {
|
||||
statsd.timing('ldap.timing.connect', new Date() - connectStartTime);
|
||||
connected = true;
|
||||
cb(null, client);
|
||||
});
|
||||
}
|
||||
|
||||
function checkOpts(required, got) {
|
||||
if (typeof got !== 'object' || got === null) {
|
||||
throw new Error("missing options object");
|
||||
}
|
||||
got = Object.keys(got);
|
||||
var missing = _.difference(required, got);
|
||||
if (missing.length) {
|
||||
throw new Error("missing required parameters: " + missing.join(', '));
|
||||
}
|
||||
}
|
||||
|
||||
/* check if we can authenticate to an ldap server. arguments include:
|
||||
* url: url to LDAP server
|
||||
* dn: LDAP distinguished name to bind
|
||||
* pass: credentials associated with DN
|
||||
*/
|
||||
exports.checkBindAuth = function(opts, cb) {
|
||||
opts.dn = opts.dn || config.get('ldap_bind_dn');
|
||||
opts.pass = opts.pass || config.get('ldap_bind_password');
|
||||
|
||||
return exports.authUser(opts, cb);
|
||||
};
|
||||
|
||||
// fetches the user's LDAP entry and returns an
|
||||
// object with mail, zimbraAlias and employeeType attributes
|
||||
function getUserData(opts, cb) {
|
||||
|
||||
// required parameters
|
||||
checkOpts([ 'email', 'dn', 'pass' ], opts);
|
||||
|
||||
// 'boundClient' is an optional parameter. When supplied,
|
||||
// we will use an existing bound LDAP connection rather than
|
||||
// opening a new one.
|
||||
|
||||
// determine the search bases
|
||||
var domain = opts.email.split('@')[1];
|
||||
var searchBases = config.get('ldap_search_bases')[domain];
|
||||
if (!searchBases) {
|
||||
process.nextTick(function() {
|
||||
cb(util.format("unsupported domain: %s", domain));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
function withClient(client) {
|
||||
/**
|
||||
* This hairy bit of code allows us to search multiple
|
||||
* base levels of the ldap directory. This exists for several
|
||||
* reasons:
|
||||
*
|
||||
* a) we want to be able to use our mock LDAP server to test with
|
||||
*
|
||||
* b) our mock ldap server (ldapjs) does not support extensible
|
||||
* filtering, otherwise we could search with this filter:
|
||||
*
|
||||
* (&(|(mail='+mail+')(zimbraAlias='+mail+'))(|(o:dn:=org)(o:dn:=com)))
|
||||
*
|
||||
* and only need one request to the server
|
||||
*
|
||||
* c) it is worth the tradeoff(?) of multiple searches, more latency,
|
||||
* more bandwidth and more complex code to have and easily
|
||||
* testable code base.
|
||||
*
|
||||
* d) it's compatible with Active Directory now ...
|
||||
*
|
||||
*/
|
||||
|
||||
function searchForEmail(searchBase, mail, searchCallback) {
|
||||
// no more bases left to search
|
||||
if (!searchBase) return searchCallback(null, []);
|
||||
|
||||
client.search(searchBase, {
|
||||
scope: 'sub',
|
||||
filter: '(|(mail='+opts.email+')(zimbraAlias='+opts.email+'))',
|
||||
attributes: ['mail', 'zimbraAlias', 'employeeType', 'pwdChangedTime']
|
||||
}, function(err, res) {
|
||||
|
||||
var results = [];
|
||||
|
||||
if (err) {
|
||||
logger.warn('error during LDAP search' + err.toString());
|
||||
return searchCallback(err, false);
|
||||
}
|
||||
|
||||
res.on('searchEntry', function(entry) {
|
||||
results.push(entry.object);
|
||||
});
|
||||
|
||||
res.on('end', function() {
|
||||
if (results.length === 0) {
|
||||
searchForEmail(searchBases.shift(), opts.email, searchCallback);
|
||||
} else {
|
||||
searchCallback(null, results);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// search searching...
|
||||
searchForEmail(searchBases.shift(), opts.email, cb);
|
||||
}
|
||||
|
||||
if (opts.boundClient) {
|
||||
withClient(opts.boundClient);
|
||||
} else {
|
||||
createClient(opts, function(err, client) {
|
||||
|
||||
// ensure unbind() is called.
|
||||
cb = _.compose(function() {
|
||||
client.unbind();
|
||||
}, cb);
|
||||
|
||||
client.bind(opts.dn, opts.pass, function(err) {
|
||||
if (err) {
|
||||
logger.warn("Could not bind to get user data");
|
||||
return cb(err, false);
|
||||
}
|
||||
|
||||
withClient(client);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// convert a (canonical) email address to a DN
|
||||
exports.convertEmailToDN = function(email) {
|
||||
// is this a supported domain?
|
||||
var searchBases = config.get('ldap_search_bases');
|
||||
var domain = email.split('@')[1];
|
||||
if (!searchBases[domain]) {
|
||||
throw new Error(util.format("unsupported domain: %s", domain));
|
||||
}
|
||||
return util.format("mail=%s,%s", email, searchBases[domain][0]);
|
||||
};
|
||||
|
||||
// given an object, add default .dn and .pass if they do not exist
|
||||
function addDefaultCredentials(opts) {
|
||||
if (!opts.dn) {
|
||||
opts.dn = config.get('ldap_bind_dn');
|
||||
if (opts.pass) throw new Error("providing a password without a DN is meaningless");
|
||||
opts.pass = config.get('ldap_bind_password');
|
||||
}
|
||||
}
|
||||
|
||||
// given an email, map it to a canonical address
|
||||
exports.canonicalAddress = function(opts, cb) {
|
||||
addDefaultCredentials(opts);
|
||||
checkOpts([ 'email', 'dn', 'pass' ], opts);
|
||||
getUserData(opts, function(err, results) {
|
||||
if (err) return cb(err, false);
|
||||
|
||||
if (results.length !== 0) {
|
||||
cb(null, results[0].mail);
|
||||
} else {
|
||||
err = "Could not find user: " + opts.email;
|
||||
logger.warn(err);
|
||||
cb(err, false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.authUser = function(opts, cb) {
|
||||
// opts.email - a user email to authenticate as
|
||||
// opts.dn - the dn to authenticate as
|
||||
|
||||
// ensure cb is called only once
|
||||
cb = _.once(cb);
|
||||
|
||||
// if email is provided, we assume it is the canonical ldap account and
|
||||
// convert it into a bind dn
|
||||
if (opts.email) {
|
||||
if (opts.dn) throw new Error(".dn and .email are mutually exclusive");
|
||||
try {
|
||||
opts.dn = exports.convertEmailToDN(opts.email);
|
||||
} catch(e) {
|
||||
process.nextTick(function() { cb(e); });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
checkOpts([ 'dn', 'pass' ], opts);
|
||||
|
||||
opts.errorCallback = function(err) {
|
||||
cb(err);
|
||||
};
|
||||
|
||||
var authStartTime = new Date();
|
||||
|
||||
// 1. connect to LDAP server
|
||||
createClient(opts, function(err, client) {
|
||||
if (err) return cb(err);
|
||||
|
||||
// ensure unbind() is called.
|
||||
cb = _.compose(function() {
|
||||
client.unbind();
|
||||
}, cb);
|
||||
|
||||
// 2. bind as target user
|
||||
client.bind(opts.dn, opts.pass, function(err) {
|
||||
statsd.timing('ldap.timing.auth', new Date() - authStartTime);
|
||||
if (err) {
|
||||
statsd.increment('ldap.auth.wrong_password');
|
||||
logger.warn('Wrong credentials for user', opts.dn, err);
|
||||
cb(err, false);
|
||||
} else {
|
||||
statsd.increment('ldap.auth.success');
|
||||
|
||||
/* only fetch extra info. if we are searching by email.
|
||||
* this happens only when a user is signing on.
|
||||
* I should also note, doing this because getUserData
|
||||
* only takes an email address to locate the *right*
|
||||
* record in our LDAP directory
|
||||
*/
|
||||
if (opts.email) {
|
||||
// fetch some info about the user
|
||||
getUserData(opts, function(err, results) {
|
||||
if (err) {
|
||||
statsd.increment('ldap.auth.fetch_data_error');
|
||||
logger.warn("Could not fetch data for user", opts.dn, err);
|
||||
cb(err, false);
|
||||
return;
|
||||
}
|
||||
|
||||
cb(null, {
|
||||
email: opts.email,
|
||||
zimbraAlias: results[0].zimbraAlias || "",
|
||||
pwdChangedTime: results[0].pwdChangedTime || ""
|
||||
});
|
||||
});
|
||||
} else {
|
||||
cb(null, {});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.userMayUseEmail = function(opts, cb) {
|
||||
// opts.user - canonical user
|
||||
// opts.email - possibly an alias
|
||||
|
||||
addDefaultCredentials(opts);
|
||||
checkOpts([ 'user', 'email', 'dn', 'pass' ], opts);
|
||||
|
||||
getUserData(opts, function(err, results) {
|
||||
if (err) return cb(err);
|
||||
|
||||
if (results.length === 0) return cb("User not found or disabled");
|
||||
|
||||
if (results[0].mail !== opts.user) {
|
||||
return cb(util.format("%s does not own not %s", opts.user, opts.email));
|
||||
}
|
||||
|
||||
if (results[0].employeetype === "DISABLED") {
|
||||
return cb(util.format("%s account is disabled"), opts.user);
|
||||
}
|
||||
|
||||
return cb(null, {
|
||||
email: opts.email,
|
||||
zimbraAlias: results[0].zimbraAlias || "",
|
||||
pwdChangedTime: results[0].pwdChangedTime || ""
|
||||
});
|
||||
});
|
||||
};
|
|
@ -1,132 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const convict = require('convict'),
|
||||
fs = require('fs'),
|
||||
path = require('path');
|
||||
|
||||
var conf = module.exports = convict({
|
||||
browserid_server: { format: "url", default: "https://login.persona.org", },
|
||||
// configuration for "cef" logging, which is used to inject application level
|
||||
// security events into syslog
|
||||
security_logging: {
|
||||
vendor: { format: 'string', default: "Mozilla" },
|
||||
product: { format: 'string', default: "mozilla-idp" },
|
||||
version: { format: 'string', default: "0.0.0" },
|
||||
syslog_tag: { format: 'string', default: "mozilla-idp" },
|
||||
syslog_host: {
|
||||
doc: 'Host where syslog service is listening',
|
||||
format: 'string',
|
||||
default: "127.0.0.1",
|
||||
env: 'CEF_SYSLOG_HOST'
|
||||
},
|
||||
syslog_port: {
|
||||
doc: 'Port on which syslog service will receive UDP messages',
|
||||
format: 'integer',
|
||||
default: 514,
|
||||
env: 'CEF_SYSLOG_PORT'
|
||||
}
|
||||
},
|
||||
certificate_validity_s: {
|
||||
doc: 'the amount of time certificates are valid',
|
||||
format: 'int',
|
||||
default: (5 * 60) // five minute default certification validity.
|
||||
},
|
||||
cookie: {
|
||||
secret: { format: 'string', default: "YOU MUST CHANGE ME" },
|
||||
duration_ms: { format: 'int', default: (24 * 60 * 60 * 1000) }
|
||||
},
|
||||
default_lang: { format: 'string', default: 'en-US' },
|
||||
debug_lang: { format: 'string', default: "it-CH" },
|
||||
domain_mapping: {
|
||||
doc: "Testing feature: Allows users to type in a testing domain to trigger the Mozilla IdP, but have their emails rewritten to mozilla domains",
|
||||
format: Object,
|
||||
default: {
|
||||
"mozilla.personatest.org": "mozilla.com"
|
||||
},
|
||||
},
|
||||
http_port: { format: 'int', env: "PORT", default: 3000 },
|
||||
http_address: { format: 'string', env: "ADDRESS", default: '127.0.0.1' },
|
||||
issuer: { format: 'string', default: "mozilla.personatest.org" },
|
||||
ldap_bind_dn: { format: 'string', default: "mail=USERNAME@mozilla.com,o=com,dc=mozilla" },
|
||||
ldap_bind_password: { format: 'string', default: "password" },
|
||||
ldap_server_url: {
|
||||
format: 'string',
|
||||
default: "ldaps://ldap.mozilla.org:636",
|
||||
env: 'LDAP_SERVER_URL'
|
||||
},
|
||||
ldap_search_bases: {
|
||||
doc: "The search bases for supported domains. Both restricts the domains we support and provides configurable LDAP search base strings",
|
||||
format: Object,
|
||||
default: {
|
||||
"mozillafoundation.org": [ "o=org,dc=mozilla", "o=com,dc=mozilla" ],
|
||||
"mozilla.com": [ "o=com,dc=mozilla", "o=org,dc=mozilla" ]
|
||||
}
|
||||
},
|
||||
ldap_server_connect_timeout: { format: 'int', default: 10000 },
|
||||
locale_directory: { format: 'string', default: "locale" },
|
||||
statsd: {
|
||||
host: { format: "string", default: "127.0.0.1" },
|
||||
port: { format: "int", default: 8125 }
|
||||
},
|
||||
supported_languages: {
|
||||
doc: "List of languages this deployment should detect and display localized strings.",
|
||||
format: Array,
|
||||
default: [ "en-US" ],
|
||||
env: 'SUPPORTED_LANGUAGES'
|
||||
},
|
||||
config_path: {
|
||||
doc: "The path where deployment specific resources, such as keys, will be sought.",
|
||||
format: 'string',
|
||||
env: 'CONFIG_PATH',
|
||||
default: ""
|
||||
},
|
||||
auth_lockout_ms: {
|
||||
doc: "The amount of time to lockout a user upon successive fail auth attemtps",
|
||||
format: 'int',
|
||||
default: (5 * 60 * 1000)
|
||||
},
|
||||
auth_lockout_attempts: {
|
||||
doc: "The number of failed authentcation attempts before a user will be locked out",
|
||||
format: 'int',
|
||||
default: 5
|
||||
},
|
||||
local_development: {
|
||||
doc: "Run in local development mode, disables secure cookies",
|
||||
format: 'boolean',
|
||||
default: false,
|
||||
env: 'LOCAL_DEV'
|
||||
}
|
||||
});
|
||||
|
||||
var dev_config_path = path.join(__dirname, '..', 'config', 'local.json');
|
||||
|
||||
if (! process.env.CONFIG_FILES &&
|
||||
fs.existsSync(dev_config_path)) {
|
||||
process.env.CONFIG_FILES = dev_config_path;
|
||||
}
|
||||
|
||||
// handle configuration files. you can specify a CSV list of configuration
|
||||
// files to process, which will be overlayed in order, in the CONFIG_FILES
|
||||
// environment variable
|
||||
if (process.env.CONFIG_FILES) {
|
||||
var files = process.env.CONFIG_FILES.split(',');
|
||||
files.forEach(function(file) {
|
||||
var c = JSON.parse(fs.readFileSync(file, 'utf8'));
|
||||
conf.load(c);
|
||||
});
|
||||
}
|
||||
|
||||
// if var path has not been set, let's default to var/
|
||||
// XXX: due to a bug in convict, .has() seems to not be working properly,
|
||||
// thus we must explicitly check for the empty string
|
||||
if (conf.get('config_path') === "") {
|
||||
conf.set('config_path', path.join(__dirname, "..", "config"));
|
||||
}
|
||||
|
||||
// massage bind address to something node will understand
|
||||
if ([ '0.0.0.0', '*' ].indexOf(conf.get('http_address')) !== -1) {
|
||||
conf.set('http_address', null);
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const jwcrypto = require("jwcrypto"),
|
||||
fs = require("fs"),
|
||||
assert = require("assert"),
|
||||
cert = jwcrypto.cert,
|
||||
config = require('./configuration');
|
||||
|
||||
// load desired algorithms
|
||||
require("jwcrypto/lib/algs/rs");
|
||||
require("jwcrypto/lib/algs/ds");
|
||||
|
||||
// TODO move these to a shared constants/config file eventually
|
||||
// and share it with scripts/gen_keys.js
|
||||
var configDir = fs.realpathSync(config.get('config_path'));
|
||||
var pubKeyFile = configDir + "/public-key.json";
|
||||
var secretKeyFile = configDir + "/secret-key.json";
|
||||
|
||||
// Load Pub/Private keys from the filesystem, exit loudly if you can't read them
|
||||
var missingFileErr = null;
|
||||
if (!fs.existsSync(pubKeyFile)) missingFileErr = "Public Key file ["+ pubKeyFile + "] does not exist";
|
||||
if (!fs.existsSync(secretKeyFile)) missingFileErr = "Secret Key file ["+secretKeyFile+"] does not exist";
|
||||
if (missingFileErr) {
|
||||
console.error('ERROR:', missingFileErr);
|
||||
console.log("\n---> run scripts/gen_keys.js to fix this\n");
|
||||
console.log();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var _privKey = jwcrypto.loadSecretKey(fs.readFileSync(secretKeyFile));
|
||||
exports.pubKey = fs.readFileSync(pubKeyFile);
|
||||
|
||||
exports.cert_key = function(pubkey, email, duration_s, cb) {
|
||||
var pubKey = jwcrypto.loadPublicKey(pubkey);
|
||||
|
||||
var expiration = new Date();
|
||||
var iat = new Date();
|
||||
|
||||
expiration.setTime(new Date().valueOf() + (duration_s * 1000));
|
||||
|
||||
// Set issuedAt to 10 seconds ago. Pads for verifier clock skew
|
||||
iat.setTime(iat.valueOf() - (10 * 1000));
|
||||
|
||||
cert.sign(
|
||||
{publicKey: pubKey, principal: {email: email}},
|
||||
{issuer: config.get('issuer'), issuedAt: iat, expiresAt: expiration},
|
||||
null,
|
||||
_privKey,
|
||||
cb);
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const config = require('./configuration.js');
|
||||
|
||||
module.exports = function(email) {
|
||||
var domainMapping = config.get('domain_mapping');
|
||||
var parts = email.split('@');
|
||||
if (parts.length === 2 && domainMapping[parts[1]]) {
|
||||
email = parts[0] + '@' + domainMapping[parts[1]];
|
||||
}
|
||||
return email;
|
||||
};
|
|
@ -1,155 +0,0 @@
|
|||
/*
|
||||
* Mock LDAP server, useful for testing and development
|
||||
*
|
||||
*/
|
||||
|
||||
const ldap = require("ldapjs");
|
||||
|
||||
module.exports = function() {
|
||||
|
||||
var i = 0;
|
||||
|
||||
// mmmmm... all passwords are "testtest"
|
||||
var directory = [
|
||||
// the vinz clortho binds as this user
|
||||
{dn: "cn=vinz, o=com, dc=mozilla", attributes: { cn: "vinz", password: "testtest" }},
|
||||
|
||||
// for testing of actual live hosts
|
||||
{dn: "mail=user@clortho.personatest.org, o=com, dc=mozilla",
|
||||
attributes: { mail: "user@clortho.personatest.org" }}
|
||||
];
|
||||
|
||||
// Create some Testing users
|
||||
for(i=1; i <= 6; i++) {
|
||||
directory.push({
|
||||
dn: "mail=user"+i+"@mozilla.com, o=com, dc=mozilla",
|
||||
attributes: {
|
||||
// note only use lowercase attribute names, seems to be an ldapjs
|
||||
// implementationd detail
|
||||
mail: "user"+i+"@mozilla.com",
|
||||
zimbraalias: 'alias'+i+"@mozilla.com",
|
||||
password:"testtest",
|
||||
employeetype: "Tester",
|
||||
pwdChangedTime: "12345Z"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for(i=1; i <= 6; i++) {
|
||||
directory.push({
|
||||
dn: "mail=user"+i+"@mozillafoundation.org, o=org, dc=mozilla",
|
||||
attributes: {
|
||||
mail: "user"+i+"@mozillafoundation.org",
|
||||
zimbraalias: 'alias'+i+"@mozillafoundation.org",
|
||||
password: "testtest",
|
||||
employeetype: "Tester",
|
||||
pwdChangedTime: "12345Z"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// add a .org mail under o=com
|
||||
directory.push({
|
||||
dn: "mail=user_a@mozilla.com, o=com, dc=mozilla",
|
||||
attributes: {
|
||||
// note only use lowercase attribute names, seems to be an ldapjs
|
||||
// implementationd detail
|
||||
mail: "user_a@mozilla.com",
|
||||
zimbraalias: 'test_a@mozillafoundation.org',
|
||||
password:"testtest",
|
||||
employeetype: "Tester",
|
||||
pwdChangedTime: "12345Z"
|
||||
}
|
||||
});
|
||||
|
||||
// add a .com under o=org
|
||||
directory.push({
|
||||
dn: "mail=user_a@mozillafoundation.org, o=org, dc=mozilla",
|
||||
attributes: {
|
||||
// note only use lowercase attribute names, seems to be an ldapjs
|
||||
// implementationd detail
|
||||
mail: "user_a@mozillafoundation.org",
|
||||
zimbraalias: 'test_a@mozilla.com',
|
||||
password:"testtest",
|
||||
employeetype: "Tester",
|
||||
pwdChangedTime: "12345Z"
|
||||
}
|
||||
});
|
||||
|
||||
function bindHandler(req, res, next) {
|
||||
var bindDN = req.dn.toString();
|
||||
var credentials = req.credentials;
|
||||
for(var i=0; i < directory.length; i++) {
|
||||
if(directory[i].dn === bindDN &&
|
||||
credentials === directory[i].attributes.password &&
|
||||
directory[i].attributes.employeetype !== 'DISABLED') {
|
||||
|
||||
this.emit('bind', {
|
||||
success: true,
|
||||
dn: bindDN,
|
||||
credentials: credentials
|
||||
});
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('bind', {
|
||||
success: false,
|
||||
dn: bindDN,
|
||||
credentials: credentials
|
||||
});
|
||||
|
||||
return next(new ldap.InvalidCredentialsError());
|
||||
}
|
||||
|
||||
function searchHandler(req, res, next) {
|
||||
directory.forEach(function(user) {
|
||||
// this test is pretty dumb, make sure in the directory
|
||||
// that things are spaced / cased exactly
|
||||
|
||||
if (user.dn.indexOf(req.dn.toString()) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.filter.matches(user.attributes)) {
|
||||
res.send(user);
|
||||
}
|
||||
});
|
||||
|
||||
res.end();
|
||||
return next();
|
||||
}
|
||||
|
||||
// some middleware to make sure the user has a successfully bind
|
||||
function authorize(req, res, next) {
|
||||
for(var i=0; i < directory.length; i++) {
|
||||
if (req.connection.ldap.bindDN.equals(directory[i].dn)) {
|
||||
this.emit('authorize', {
|
||||
success: true,
|
||||
dn: req.connection.ldap.bindDN
|
||||
});
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('authorize', {
|
||||
success: false,
|
||||
dn: req.connection.ldap.bindDN
|
||||
});
|
||||
|
||||
return next(new ldap.InsufficientAccessRightsError());
|
||||
}
|
||||
|
||||
var ldapServer = ldap.createServer();
|
||||
ldapServer.bind('dc=mozilla', bindHandler);
|
||||
ldapServer.search('dc=mozilla', [authorize], searchHandler);
|
||||
|
||||
return {
|
||||
directory: directory,
|
||||
server: ldapServer,
|
||||
bindHandler: bindHandler,
|
||||
searchHandler: searchHandler
|
||||
};
|
||||
};
|
|
@ -1,14 +0,0 @@
|
|||
/* a tiny wrapper around winston that let's us route all logging in the
|
||||
* application through winston, and at a later point
|
||||
* do more complex things with logging configuration per environment if
|
||||
* needed */
|
||||
|
||||
// simply export winston
|
||||
exports.logger = require('winston');
|
||||
|
||||
// enable logging of uncaught exceptions
|
||||
exports.logger.handleExceptions(new exports.logger.transports.Console());
|
||||
|
||||
exports.disable = function() {
|
||||
exports.logger.remove(exports.logger.transports.Console);
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
var useragent = require('useragent'),
|
||||
policy = 'CP="This is not a P3P policy, but Mozilla deeply cares about ' +
|
||||
'your privacy. See http://www.mozilla.org/persona/privacy-policy ' +
|
||||
'for more."';
|
||||
|
||||
// add 'P3P' headers so that IE8, with default security settings, will allow
|
||||
// us to set third-party cookies. Only add the headers in that case, saving
|
||||
// bytes for all the other browsers. #2340
|
||||
module.exports = function(req, res, next) {
|
||||
var browser = useragent.parse(req.headers['user-agent']);
|
||||
if (browser.family === 'IE') {
|
||||
res.on('header', function() {
|
||||
res.setHeader('P3P', policy);
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* Security logging via 'CEF' is intended to report security events
|
||||
* that could not be caused by non-malicious usage of the system.
|
||||
*
|
||||
* Some basic guidance with respect to "what to log" are here:
|
||||
* - https://wiki.mozilla.org/Security/Users_and_Logs#What_to_Log
|
||||
*
|
||||
* With respect to the Mozilla IdP, event to log will be of the
|
||||
* nature - 5 password failures in a row.
|
||||
*/
|
||||
|
||||
var cef = require('cef'),
|
||||
conf = require('./configuration.js');
|
||||
|
||||
module.exports = new cef.Logger(conf.get('security_logging'));
|
||||
|
||||
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const
|
||||
StatsD = require("node-statsd").StatsD,
|
||||
config = require('./configuration'),
|
||||
logger = require('./logging').logger;
|
||||
|
||||
// report statistics with a "mozillaidp" prefix.
|
||||
const PREFIX = "mozillaidp.";
|
||||
|
||||
var statsd = new StatsD(config.get('statsd.host'), config.get('statsd.port'));
|
||||
|
||||
// start by exporting a stubbed no-op stats reporter
|
||||
module.exports = {
|
||||
timing: function(s, v) {
|
||||
if (statsd) { statsd.timing(PREFIX + s, v); }
|
||||
},
|
||||
increment: function(s, v) {
|
||||
if (statsd) { statsd.increment(PREFIX + s, v); }
|
||||
}
|
||||
};
|
|
@ -1,54 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/* A simple in-memory throttle for failed authentication. This being
|
||||
* in memory means a couple things:
|
||||
* * For multiple server deployments, authenticationt throttling
|
||||
* counts are node-local
|
||||
* * client code should not add failed authentication attempts
|
||||
* for non-existant users, lest a remote attacker can fatally bloat
|
||||
* process memory
|
||||
*/
|
||||
|
||||
var config = require('./configuration');
|
||||
|
||||
/* keep track of failed authentication attempt. Keys are email addresses,
|
||||
* values include `time` (time of last failure) and `count` (the number
|
||||
* of consecutive failures */
|
||||
var fails = {};
|
||||
|
||||
exports.check = function(email, cb) {
|
||||
if (fails[email]) {
|
||||
if ((new Date() - fails[email].time) > config.get('auth_lockout_ms')) {
|
||||
delete fails[email];
|
||||
}
|
||||
}
|
||||
|
||||
if (!cb) return;
|
||||
|
||||
process.nextTick(function() {
|
||||
if (fails[email] &&
|
||||
fails[email].count >= config.get('auth_lockout_attempts')) {
|
||||
cb("lockout");
|
||||
} else {
|
||||
cb(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
exports.failed = function(email, cb) {
|
||||
if (!fails[email]) {
|
||||
fails[email] = { count: 0 };
|
||||
}
|
||||
|
||||
fails[email].count++;
|
||||
fails[email].time = new Date();
|
||||
|
||||
if (cb) process.nextTick(cb);
|
||||
};
|
||||
|
||||
exports.clear = function(email, cb) {
|
||||
if (fails[email]) delete fails[email];
|
||||
if (cb) process.nextTick(cb);
|
||||
};
|
|
@ -1,145 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// a teensy tinsy module to do parameter validation. A good candiate for future
|
||||
// librification.
|
||||
//
|
||||
// usage:
|
||||
//
|
||||
// const validate = require('validate.js');
|
||||
//
|
||||
// app.post('/wsapi/foo', validate({
|
||||
// email: 'email',
|
||||
// site: "origin"
|
||||
// }), function(req, resp) {
|
||||
// // handler that uses .params
|
||||
// });
|
||||
|
||||
const
|
||||
logger = require('./logging.js').logger,
|
||||
check = require('validator').check;
|
||||
|
||||
var hostnameRegex = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$|^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/;
|
||||
|
||||
var types = {
|
||||
email: function(x) {
|
||||
check(x).isEmail();
|
||||
},
|
||||
email_type: function(x) {
|
||||
check(x).isIn([ 'primary', 'secondary' ]);
|
||||
},
|
||||
password: function(x) {
|
||||
check(x).len(6,80);
|
||||
},
|
||||
boolean: function(x) {
|
||||
if (typeof x !== 'boolean') throw "boolean required";
|
||||
},
|
||||
token: function(x) {
|
||||
check(x).len(48,48).isAlphanumeric();
|
||||
},
|
||||
assertion: function(x) {
|
||||
check(x).len(50,10240).regex(/[0-9a-zA-Z~_\-]+/);
|
||||
},
|
||||
pubkey: function(x) {
|
||||
check(x).len(50,10240);
|
||||
JSON.parse(x);
|
||||
},
|
||||
hostname: function(x) {
|
||||
check(x).is(hostnameRegex);
|
||||
},
|
||||
origin: function(x) {
|
||||
/* origin regex
|
||||
/^ // beginning
|
||||
https?:\/\/ // starts with http:// or https://
|
||||
(?=.{1,254}(?::|$)) // hostname must be within 1-254 characters
|
||||
(?: // match hostname part (<part>.<part>...)
|
||||
(?!-) // cannot start with a dash (allow it to start with a digit re issue #2042)
|
||||
(?![a-z0-9\-]{1,62}- // part cannot end with a dash
|
||||
(?:\.|:|$)) // (end of part will be '.', ':', or end of str)
|
||||
[a-z0-9\-]{1,63}\b // part will be 1-63 letters, numbers, or dashes
|
||||
(?!\.$) // final part cannot end with a '.'
|
||||
\.? // part followed by '.' unless final part
|
||||
)+ // one or more hostname parts
|
||||
(:\d+)? // optional port
|
||||
$/i; // end; case-insensitive
|
||||
*/
|
||||
var regex = /^https?:\/\/(?=.{1,254}(?::|$))(?:(?!-)(?![a-z0-9\-]{1,62}-(?:\.|:|$))[a-z0-9\-]{1,63}\b(?!\.$)\.?)+(:\d+)?$/i;
|
||||
if (typeof x !== 'string' || !x.match(regex)) {
|
||||
throw new Error("not a valid origin");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = function (params) {
|
||||
// normalize the parameters description, verify all specified types are present
|
||||
if (Array.isArray(params) || typeof params !== 'object' || typeof params === null) {
|
||||
throw "argument to validate must be an object, not a " + (typeof params);
|
||||
}
|
||||
|
||||
Object.keys(params).forEach(function(p) {
|
||||
var v = params[p];
|
||||
if (typeof v === 'string') {
|
||||
v = { type: v };
|
||||
}
|
||||
if (typeof v.required === "undefined") v.required = true;
|
||||
|
||||
if (!types[v.type]) throw "unknown type specified in WSAPI:" + v.type;
|
||||
params[p] = v;
|
||||
});
|
||||
|
||||
return function(req, resp, next) {
|
||||
var reqParams = null;
|
||||
if (req.method === "POST") {
|
||||
reqParams = req.body;
|
||||
} else {
|
||||
reqParams = req.query;
|
||||
}
|
||||
|
||||
// clear body and query to prevent wsapi handlers from accessing
|
||||
// un-validated input parameters
|
||||
req.body = {};
|
||||
req.query = {};
|
||||
req.params = {};
|
||||
|
||||
// now validate
|
||||
try {
|
||||
// allow csrf through
|
||||
if (reqParams.csrf) {
|
||||
req.params.csrf = reqParams.csrf;
|
||||
delete reqParams.csrf;
|
||||
}
|
||||
|
||||
Object.keys(params).forEach(function(p) {
|
||||
if (params[p].required && !reqParams.hasOwnProperty(p)) throw "missing required parameter: '" + p + "'";
|
||||
if (reqParams[p] === undefined) return;
|
||||
|
||||
// validate
|
||||
try {
|
||||
types[params[p].type](reqParams[p]);
|
||||
} catch (e) {
|
||||
throw p + ": " + e.toString();
|
||||
}
|
||||
req.params[p] = reqParams[p];
|
||||
delete reqParams[p];
|
||||
});
|
||||
|
||||
// if there are any keys left in reqParams, they're not allowable!
|
||||
var extra = Object.keys(reqParams);
|
||||
if (extra.length) throw "unsupported parameter: '" + extra.join("', '") + "'";
|
||||
} catch(e) {
|
||||
var msg = {
|
||||
success: false,
|
||||
reason: e.toString()
|
||||
};
|
||||
logger.warn("bad request received: " + msg.reason);
|
||||
resp.statusCode = 400;
|
||||
return resp.json(msg);
|
||||
}
|
||||
|
||||
|
||||
// this is called outside the try/catch because errors
|
||||
// in the handling of the request should be caught separately
|
||||
next();
|
||||
};
|
||||
};
|
240
server/routes.js
|
@ -1,240 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const config = require('./lib/configuration'),
|
||||
crypto = require('./lib/crypto'),
|
||||
util = require('util'),
|
||||
emailRewrite = require('./lib/email_rewrite.js'),
|
||||
auth = require('./lib/auth'),
|
||||
ldap = require('ldapjs'),
|
||||
logger = require('./lib/logging.js').logger,
|
||||
statsd = require('./lib/statsd'),
|
||||
throttle = require('./lib/throttle'),
|
||||
secLog = require('./lib/security_logging'),
|
||||
url = require('url');
|
||||
|
||||
// apply X-Content-Security-Policy headers to HTML resources served
|
||||
function applyContentSecurityPolicy(res) {
|
||||
['X-Content-Security-Policy','Content-Security-Policy'].forEach(function(header) {
|
||||
res.setHeader(header,
|
||||
util.format("default-src 'self' %s",
|
||||
config.get('browserid_server')));
|
||||
});
|
||||
}
|
||||
|
||||
exports.routes = function () {
|
||||
return {
|
||||
well_known_browserid: function (req, resp) {
|
||||
var pk = crypto.pubKey;
|
||||
resp.setHeader('Content-Type', 'application/json');
|
||||
resp.setHeader('Cache-Control', 'max-age=5, public');
|
||||
resp.render('well_known_browserid', {
|
||||
public_key: pk,
|
||||
layout: false
|
||||
});
|
||||
},
|
||||
provision: function (req, resp) {
|
||||
statsd.increment('provision.attempt');
|
||||
applyContentSecurityPolicy(resp);
|
||||
// This cookie tests 3rd party Cookie blocking
|
||||
req.session.question = "Are you the Keymaster?";
|
||||
resp.render('provision', {
|
||||
user: req.session.email,
|
||||
browserid_server: config.get('browserid_server'),
|
||||
layout: false
|
||||
});
|
||||
},
|
||||
signin: function (req, resp) {
|
||||
if (! req.session.question) {
|
||||
statsd.increment('auth.3rd_party_cookies_fail');
|
||||
return resp.render('3rd_party_cookies',
|
||||
{ title: req.gettext("Sign In") });
|
||||
}
|
||||
|
||||
statsd.increment('auth.attempt');
|
||||
var email = (req.query ? req.query.email : null);
|
||||
if (email) email = emailRewrite(email);
|
||||
|
||||
// prevent framing of authentication page.
|
||||
resp.setHeader('X-Frame-Options', 'DENY');
|
||||
applyContentSecurityPolicy(resp);
|
||||
resp.render('signin', {
|
||||
title: req.gettext("Sign In"),
|
||||
email: email
|
||||
});
|
||||
},
|
||||
|
||||
welcome: function (req, resp) {
|
||||
resp.setHeader('X-Frame-Options', 'DENY');
|
||||
applyContentSecurityPolicy(resp);
|
||||
resp.render('welcome', {
|
||||
title: req.gettext("The Mozilla Identity Provider")
|
||||
});
|
||||
},
|
||||
|
||||
handle404: function (req, resp) {
|
||||
resp.setHeader('X-Frame-Options', 'DENY');
|
||||
applyContentSecurityPolicy(resp);
|
||||
resp.render('404', {
|
||||
title: req.gettext("No Content Found"),
|
||||
status: 404
|
||||
});
|
||||
},
|
||||
|
||||
// API end points
|
||||
provision_key: function (req, resp) {
|
||||
// check that there is an authenticated user
|
||||
if (!req.session || !req.session.email) {
|
||||
return resp.send('No Session', 401);
|
||||
}
|
||||
// check that required arguments are supplied
|
||||
if (!req.params.pubkey || !req.params.user) {
|
||||
return resp.send(400);
|
||||
}
|
||||
|
||||
// check that the user is authenticated as the target user
|
||||
auth.userMayUseEmail({
|
||||
user: req.session.email,
|
||||
email: emailRewrite(req.params.user).toLowerCase()
|
||||
}, function(err, userData) {
|
||||
if (err) {
|
||||
logger.warn("cannot provision user:", err);
|
||||
statsd.increment('provision.failure');
|
||||
return resp.send(401);
|
||||
}
|
||||
|
||||
// if the user has changed their password since the last
|
||||
// provision then force them to log in again
|
||||
if (userData.pwdChangedTime !== req.session.pwdChangedTime) {
|
||||
statsd.increment('provision.pwdChangedTime mismatch');
|
||||
req.session.reset();
|
||||
return resp.send('Password Changed. Reauthentication required.', 401);
|
||||
}
|
||||
|
||||
crypto.cert_key(
|
||||
req.params.pubkey,
|
||||
req.params.user,
|
||||
config.get('certificate_validity_s'),
|
||||
function(err, cert) {
|
||||
if (err) {
|
||||
resp.writeHead(500);
|
||||
resp.end();
|
||||
} else {
|
||||
// successful provisioning
|
||||
statsd.increment('provision.success');
|
||||
resp.json({ cert: cert });
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
check_signin: function (req, resp) {
|
||||
var mozillaUser = "";
|
||||
if (req.params.user) {
|
||||
mozillaUser = emailRewrite(req.params.user).toLowerCase();
|
||||
}
|
||||
|
||||
if (!req.params.user || !req.params.pass) {
|
||||
resp.writeHead(400);
|
||||
return resp.end();
|
||||
} else {
|
||||
auth.canonicalAddress({ email: mozillaUser }, function(err, mozillaUser) {
|
||||
if (err) {
|
||||
resp.json({
|
||||
success: false,
|
||||
reason: 'email not found'
|
||||
}, 401);
|
||||
return;
|
||||
}
|
||||
|
||||
throttle.check(mozillaUser, function(err) {
|
||||
if (err) {
|
||||
// Send an event to the security log for every authentication
|
||||
// attempt to a throttle account.
|
||||
secLog.warn({
|
||||
signature: 'AUTH_LOCKOUT',
|
||||
name: "attempted login to a throttled account",
|
||||
extensions: {
|
||||
suser: mozillaUser
|
||||
}
|
||||
});
|
||||
statsd.increment('auth.throttle');
|
||||
// as per security guidelines, account throttling should
|
||||
// be indistiguishable from wrong password. This is a
|
||||
// usability loss in the name of security
|
||||
// https://wiki.mozilla.org/WebAppSec/Secure_Coding_Guidelines
|
||||
resp.json({
|
||||
success: false,
|
||||
reason: 'email or password incorrect'
|
||||
}, 401);
|
||||
return;
|
||||
}
|
||||
auth.authUser({
|
||||
email: mozillaUser,
|
||||
pass: req.params.pass
|
||||
}, function (err, userData) {
|
||||
if (err || userData === false) {
|
||||
// if this is a password failure, note it in our password
|
||||
// throttling
|
||||
if (err && err.name === 'InvalidCredentialsError') {
|
||||
throttle.failed(mozillaUser);
|
||||
}
|
||||
resp.json({
|
||||
success: false,
|
||||
reason: 'email or password incorrect'
|
||||
}, 401);
|
||||
} else {
|
||||
// upon successful authentication, clear any throttling
|
||||
// for this user
|
||||
throttle.clear(mozillaUser);
|
||||
|
||||
req.session.email = mozillaUser;
|
||||
req.session.pwdChangedTime = userData.pwdChangedTime;
|
||||
|
||||
resp.send({ success: true }, 200);
|
||||
statsd.increment('auth.success');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
session_context: function(req, res) {
|
||||
res.json({
|
||||
csrf: req.session._csrf
|
||||
}, 200);
|
||||
},
|
||||
|
||||
// Monitoring End points
|
||||
|
||||
// the ELB (elastic load balancer) check is just to make
|
||||
// sure that node returns a response
|
||||
elb_check: function(req, res) {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
res.send("OK");
|
||||
},
|
||||
|
||||
// checks that we can bind against the LDAP server
|
||||
// this check is for our global load balancers so they
|
||||
// can add / remove regions if LDAP connectivity drops
|
||||
checkStatus: function(req, res) {
|
||||
auth.checkBindAuth({}, function(err) {
|
||||
res.setHeader('Content-Type', 'text/plain');
|
||||
if (err) {
|
||||
statsd.increment('healthcheck.error');
|
||||
// try message, no? has name? no ... "unknown"
|
||||
var output = "Error: " + err.name;
|
||||
res.send(output, 503);
|
||||
} else {
|
||||
statsd.increment('healthcheck.ok');
|
||||
res.send('OK');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// QA Only URLs
|
||||
signout: function (req, resp) { req.session.reset(); resp.redirect('/'); }
|
||||
};
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
<div class="title">
|
||||
<img src="/i/mozilla-wordmark-200.png" alt="<%= gettext("Mozilla Logo") %>" />
|
||||
</div>
|
||||
<div class="signin-panel">
|
||||
|
||||
<h1><%= gettext("Error:") %></h1>
|
||||
<p><%= gettext("login.mozilla.org cookies blocked") %></p>
|
||||
|
||||
<p class="error"><%- format(gettext('You need third-party cookies enabled. <a href="%s">Read More</a>.'),
|
||||
["https://support.mozilla.org/en-US/kb/enable-and-disable-cookies-website-preferences?esab=a&s=3rd+party+cookies&r=2&as=s"]) %></p>
|
||||
|
||||
</div><!-- #sign-in-panel -->
|
|
@ -1,8 +0,0 @@
|
|||
<div class="title">
|
||||
<img src="/i/mozilla-wordmark-200.png" alt="<%= gettext("Mozilla Logo") %>" />
|
||||
</div>
|
||||
<div class="signin-panel">
|
||||
<div class="prompt">
|
||||
<p><%= gettext("No content found at this URL! So Sorry.") %></p>
|
||||
</div>
|
||||
</div>
|
|
@ -1,17 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="<%= lang %>" dir="<%= lang_dir %>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><%= title %></title>
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<link rel="stylesheet" href="/css/stylo.css">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<link href="/opensans-regular,opensans-light/fonts.css" type="text/css" rel="stylesheet"/ >
|
||||
</head>
|
||||
<body>
|
||||
<%- body %>
|
||||
<script src="<%= browserid_server %>/authentication_api.js"></script>
|
||||
<script src="/js/lib/jquery.min.js"></script>
|
||||
<script src="/js/signin.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>provisioning</title>
|
||||
</head>
|
||||
<body>
|
||||
<input type="hidden" name="_csrf" value="<%= csrf_token %>" />
|
||||
<script type="text/javascript" src="<%= browserid_server %>/provisioning_api.js"></script>
|
||||
<script type="text/javascript" src="/js/lib/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="/js/provision.js" /></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,35 +0,0 @@
|
|||
<div class="title">
|
||||
<img src="/i/mozilla-wordmark-200.png" alt="<%= gettext("Mozilla Logo") %>" />
|
||||
</div>
|
||||
<div class="signin-panel">
|
||||
<div class="prompt">
|
||||
<p><%= gettext("Please use your LDAP password") %></p>
|
||||
</div>
|
||||
|
||||
<form action="/api/sign_in" method="POST" novalidate>
|
||||
|
||||
<input type="hidden" name="_csrf" value="<%= csrf_token %>" />
|
||||
<div class="center-col">
|
||||
<label class="hidden" for="user"><%= gettext("Email address") %></label>
|
||||
<input id="user" type="email" name="user" value="<%= email %>"
|
||||
autocorrect="off" autocapitalize="off" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="center-col">
|
||||
<label class="hidden" for="pass"><%= gettext("password") %></label>
|
||||
<input type="password" id="pass" name="pass" value="" autofocus />
|
||||
</div>
|
||||
|
||||
<div class="center-col last clearfix">
|
||||
<button><%= gettext('sign in') %></button>
|
||||
<button class="cancel"><%= gettext('cancel') %></button>
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- #sign-in-panel -->
|
||||
|
||||
<div class="tooltip">
|
||||
<span class="contents"></span>
|
||||
<div class="arrow-down"></div>
|
||||
</div>
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<div class="title">
|
||||
<img src="/i/mozilla-wordmark-200.png" alt="<%= gettext("Mozilla Logo") %>" />
|
||||
</div>
|
||||
<div class="overview">
|
||||
<p><strong><%= gettext("The Mozilla Identity Provider") %></strong>:
|
||||
<em><%= gettext("Better LDAP authentication for Mozilla employees since 2013.") %></em>
|
||||
</p>
|
||||
<p>
|
||||
<%- gettext('To use this, log in with your Mozilla email address anywhere <a href="https://persona.org">Persona</a> is supported.') %>
|
||||
</p>
|
||||
</div>
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"public-key": <%- public_key %>,
|
||||
"authentication": "/sign_in",
|
||||
"provisioning": "/provision"
|
||||
}
|
|
@ -1,189 +0,0 @@
|
|||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframe,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
strong,
|
||||
tt,
|
||||
var,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
vertical-align: baseline;
|
||||
background: transparent;
|
||||
font-weight: inherit;
|
||||
font-style: inherit;
|
||||
font-size: 100%;
|
||||
font-family: inherit;
|
||||
}
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
position: relative;
|
||||
}
|
||||
.outer {
|
||||
float: left;
|
||||
clear: both;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
}
|
||||
.inner {
|
||||
position: relative;
|
||||
left: -50%;
|
||||
}
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: helvetica, arial, verdana, sans-serif;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
.floatClear {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
.clearfix {
|
||||
zoom: 1;
|
||||
}
|
||||
.clearfix:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
#p404 {
|
||||
color: #FFF;
|
||||
background-color: #021D48;
|
||||
line-height: 1.5;
|
||||
font-family: Times;
|
||||
}
|
||||
#p404 div {
|
||||
width: 1000px;
|
||||
margin: auto;
|
||||
margin-top: 15%;
|
||||
border: solid 1px white;
|
||||
}
|
||||
#p404 div img {
|
||||
width: 573px;
|
||||
float: left;
|
||||
}
|
||||
#p404 div div {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 398px;
|
||||
margin: 0 auto;
|
||||
margin-left: 537px;
|
||||
height: 300px;
|
||||
border: none;
|
||||
vertical-align: bottom;
|
||||
text-align: center;
|
||||
}
|
||||
#p404 div div blockquote {
|
||||
width: 358px;
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
margin: 10 auto;
|
||||
}
|
||||
#p404 div div blockquote em {
|
||||
font-style: italic;
|
||||
}
|
||||
#p404 div div blockquote,
|
||||
#p404 div div p {
|
||||
padding-left: 38px;
|
||||
}
|
||||
#p404 div div p {
|
||||
margin-top: 10px;
|
||||
}
|
||||
#p404 div .clear {
|
||||
clear: both;
|
||||
}
|
|
@ -1,552 +0,0 @@
|
|||
html,
|
||||
body,
|
||||
div,
|
||||
span,
|
||||
applet,
|
||||
object,
|
||||
iframe,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p,
|
||||
blockquote,
|
||||
pre,
|
||||
a,
|
||||
abbr,
|
||||
acronym,
|
||||
address,
|
||||
big,
|
||||
cite,
|
||||
code,
|
||||
del,
|
||||
dfn,
|
||||
em,
|
||||
img,
|
||||
ins,
|
||||
kbd,
|
||||
q,
|
||||
s,
|
||||
samp,
|
||||
small,
|
||||
strike,
|
||||
strong,
|
||||
tt,
|
||||
var,
|
||||
center,
|
||||
dl,
|
||||
dt,
|
||||
dd,
|
||||
ol,
|
||||
ul,
|
||||
li,
|
||||
fieldset,
|
||||
form,
|
||||
label,
|
||||
legend,
|
||||
table,
|
||||
caption,
|
||||
tbody,
|
||||
tfoot,
|
||||
thead,
|
||||
tr,
|
||||
th,
|
||||
td,
|
||||
article,
|
||||
aside,
|
||||
canvas,
|
||||
details,
|
||||
embed,
|
||||
figure,
|
||||
figcaption,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
output,
|
||||
ruby,
|
||||
section,
|
||||
summary,
|
||||
time,
|
||||
mark,
|
||||
audio,
|
||||
video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
vertical-align: baseline;
|
||||
background: transparent;
|
||||
font-weight: inherit;
|
||||
font-style: inherit;
|
||||
font-size: 100%;
|
||||
font-family: inherit;
|
||||
}
|
||||
article,
|
||||
aside,
|
||||
details,
|
||||
figcaption,
|
||||
figure,
|
||||
footer,
|
||||
header,
|
||||
hgroup,
|
||||
menu,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-o-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.outer {
|
||||
float: left;
|
||||
clear: both;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
}
|
||||
.inner {
|
||||
position: relative;
|
||||
left: -50%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Open Sans', 'sans-serif', 'serif';
|
||||
text-rendering: optimizeLegibility;
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.floatClear {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
.clearfix {
|
||||
zoom: 1;
|
||||
}
|
||||
.clearfix:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
/* === Layout === */
|
||||
body {
|
||||
line-height: 1.5;
|
||||
}
|
||||
div.title {
|
||||
width: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.signin-panel {
|
||||
border: solid 0px #ff0000;
|
||||
width: 325px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
.left-col {
|
||||
clear: left;
|
||||
text-align: right;
|
||||
}
|
||||
html[dir=rtl] .left-col {
|
||||
clear: right;
|
||||
text-align: left;
|
||||
}
|
||||
.left-col {
|
||||
border: solid 0px #0000ff;
|
||||
min-width: 113px;
|
||||
max-width: 173px;
|
||||
min-height: 1px;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
}
|
||||
html[dir=rtl] .left-col,
|
||||
html[dir=rtl] .right-col,
|
||||
html[dir=rtl] .center-col {
|
||||
float: right;
|
||||
}
|
||||
.prompt {
|
||||
text-align: center;
|
||||
margin-bottom: 1em;
|
||||
font-size: 1.1em;
|
||||
font-weight: 300;
|
||||
}
|
||||
.center-col {
|
||||
width: 200px;
|
||||
margin: auto;
|
||||
margin-top: .5em;
|
||||
}
|
||||
.center-col.last {
|
||||
margin-top: 1em;
|
||||
position: relative;
|
||||
}
|
||||
.clear {
|
||||
clear: both;
|
||||
}
|
||||
button {
|
||||
font-size: .8em;
|
||||
font-weight: bold;
|
||||
padding: .3em 1.8em .4em;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
-moz-border-radius: .25em;
|
||||
border-radius: .25em;
|
||||
box-shadow: 0 1px 0 0 rgba(0,0,0,0.2),inset 0 -1px 0 0 rgba(0,0,0,0.3);
|
||||
background-color: #43a6e2;
|
||||
background-color: #277ac1;
|
||||
background-repeat: repeat-x;
|
||||
background-image: -khtml-gradient(linear,left top,left bottom,from(#43a6e2),to(#277ac1));
|
||||
background-image: -moz-linear-gradient(#43a6e2,#277ac1);
|
||||
background-image: -ms-linear-gradient(#43a6e2,#277ac1);
|
||||
background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0%,#43a6e2),color-stop(100%,#277ac1));
|
||||
background-image: -webkit-linear-gradient(#43a6e2,#277ac1);
|
||||
background-image: -o-linear-gradient(#43a6e2,#277ac1);
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#43a6e2', endColorstr='#277ac1', GradientType=0)";
|
||||
background-image: linear-gradient(#43a6e2,#277ac1);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
text-shadow: 0 1px 0 rgba(0,0,0,0.25);
|
||||
cursor: pointer;
|
||||
float: left;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button:focus,
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.3), 0 1px 0 rgba(0, 0, 0, 0.2), 0 2px 0 rgba(0, 0, 0, 0.1);
|
||||
|
||||
background-color: #4aafe5;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#4aafe5), to(#2c89c8));
|
||||
background-image: -webkit-linear-gradient(top, #4aafe5, #2c89c8);
|
||||
background-image: -moz-linear-gradient(top, #4aafe5, #2c89c8);
|
||||
background-image: -ms-linear-gradient(top, #4aafe5, #2c89c8);
|
||||
background-image: -o-linear-gradient(top, #4aafe5, #2c89c8);
|
||||
background-image: linear-gradient(top, #4aafe5, #2c89c8);
|
||||
}
|
||||
|
||||
button:focus,
|
||||
.button:focus {
|
||||
box-shadow: 0 0 1px #fff, 0 0 1px 3px #49ADE3;
|
||||
box-shadow: 0 0 1px rgba(255, 255, 255, 0.5), 0 0 1px 3px rgba(73, 173, 227, 0.6);
|
||||
}
|
||||
|
||||
button:active,
|
||||
.button:active {
|
||||
background-color: #184a73;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#184a73), to(#276084));
|
||||
background-image: -webkit-linear-gradient(top, #184a73, #276084);
|
||||
background-image: -moz-linear-gradient(top, #184a73, #276084);
|
||||
background-image: -ms-linear-gradient(top, #184a73, #276084);
|
||||
background-image: -o-linear-gradient(top, #184a73, #276084);
|
||||
background-image: linear-gradient(top, #184a73, #276084);
|
||||
color: #97b6ca;
|
||||
text-shadow: 0 1px rgba(0,0,0,0.4);
|
||||
box-shadow: inset 0 2px 1px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
button::-moz-focus-inner, .button::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0
|
||||
}
|
||||
|
||||
button.cancel {
|
||||
float: right;
|
||||
background-color: #d94f30;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#d94f30), to(#ad1804));
|
||||
background-image: -webkit-linear-gradient(top, #d94f30, #ad1804);
|
||||
background-image: -moz-linear-gradient(top, #d94f30, #ad1804);
|
||||
background-image: -ms-linear-gradient(top, #d94f30, #ad1804);
|
||||
background-image: -o-linear-gradient(top, #d94f30, #ad1804);
|
||||
background-image: linear-gradient(top, #d94f30, #ad1804);
|
||||
filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#d93f30, endColorstr=#ad1804)";
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#d93f30, endColorstr=#ad1804)";
|
||||
}
|
||||
|
||||
button.cancel:hover,
|
||||
button.cancel:focus {
|
||||
background-color: #e3653f;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#e3653f), to(#c01c03));
|
||||
background-image: -webkit-linear-gradient(top, #e3653f, #c01c03);
|
||||
background-image: -moz-linear-gradient(top, #e3653f, #c01c03);
|
||||
background-image: -ms-linear-gradient(top, #e3653f, #c01c03);
|
||||
background-image: -o-linear-gradient(top, #e3653f, #c01c03);
|
||||
background-image: linear-gradient(top, #e3653f, #c01c03);
|
||||
}
|
||||
|
||||
button.cancel:active,
|
||||
.button.cancel:active {
|
||||
box-shadow: 0 0 5px #333 inset;
|
||||
color: #cfa391;
|
||||
|
||||
background-color: #83311e;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#83311e), to(#670d01));
|
||||
background-image: -webkit-linear-gradient(top, #83311e, #670d01);
|
||||
background-image: -moz-linear-gradient(top, #83311e, #670d01);
|
||||
background-image: -ms-linear-gradient(top, #83311e, #670d01);
|
||||
background-image: -o-linear-gradient(top, #83311e, #670d01);
|
||||
background-image: linear-gradient(top, #83311e, #670d01);
|
||||
}
|
||||
|
||||
|
||||
/* === Basic Style === */
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Open Sans', 'sans-serif', 'serif';
|
||||
background-image: linear-gradient(bottom, rgb(62,72,79) 39%, rgb(91,104,112) 70%);
|
||||
background-image: -o-linear-gradient(bottom, rgb(62,72,79) 39%, rgb(91,104,112) 70%);
|
||||
background-image: -moz-linear-gradient(bottom, rgb(62,72,79) 39%, rgb(91,104,112) 70%);
|
||||
background-image: -webkit-linear-gradient(bottom, rgb(62,72,79) 39%, rgb(91,104,112) 70%);
|
||||
background-image: -ms-linear-gradient(bottom, rgb(62,72,79) 39%, rgb(91,104,112) 70%);
|
||||
|
||||
background-image: -webkit-gradient(
|
||||
linear,
|
||||
left bottom,
|
||||
left top,
|
||||
color-stop(0.39, rgb(62,72,79)),
|
||||
color-stop(0.7, rgb(91,104,112))
|
||||
);
|
||||
background-color: #4D4E53;
|
||||
background-repeat: no-repeat;
|
||||
overflow-y: auto;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ddd;
|
||||
text-decoration: none;
|
||||
border-bottom:1px dashed;
|
||||
}
|
||||
|
||||
.overview {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
font-size: 1.2em;
|
||||
text-align: center;
|
||||
}
|
||||
.overview p {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.signin-panel {
|
||||
color: #555;
|
||||
background-color: #FFF;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
-moz-box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.25), 3px 3px 60px rgba(0, 0, 0, 0.25), 5px 2px 16px rgba(0, 0, 0, 0.25);
|
||||
-webkit-box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.25), 3px 3px 60px rgba(0, 0, 0, 0.25), 5px 2px 16px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: 2px 2px 6px rgba(0, 0, 0, 0.25), 3px 3px 60px rgba(0, 0, 0, 0.25), 5px 2px 16px rgba(0, 0, 0, 0.25);
|
||||
background: #f7f7f7;
|
||||
background-image: -moz-linear-gradient(top, #ffffff, #ffffff 40%, #eeeeee);
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), color-stop(0.4, #ffffff), to(#eeeeee));
|
||||
background-image: -webkit-linear-gradient(top, from(#ffffff), color-stop(0.4, #ffffff), to(#eeeeee));
|
||||
background-image: -o-linear-gradient(top, #ffffff 0%, #ffffff 40%, #eeeeee 100%);
|
||||
filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff, endColorstr=#eeeeee)";
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#ffffff, endColorstr=#eeeeee)";
|
||||
background-image: linear-gradient(top, #ffffff 0%, #ffffff 40%, #eeeeee 100%);
|
||||
}
|
||||
|
||||
label {
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
/* This funkiness is so that labels still appear for screen readers */
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
}
|
||||
.logo {
|
||||
padding-top: 10px;
|
||||
}
|
||||
/* === Heading === */
|
||||
h1 {
|
||||
font-size: 1.5em;
|
||||
margin-bottom: -0.3em;
|
||||
}
|
||||
/* === Labels === */
|
||||
label {
|
||||
font-weight: bold;
|
||||
}
|
||||
/* === Inputs === */
|
||||
input {
|
||||
width: 100%;
|
||||
border: 1px solid #666;
|
||||
padding: .4em .5em;
|
||||
-moz-border-radius: .3em;
|
||||
border-radius: .3em;
|
||||
font-size: .8em;
|
||||
border-color: #b2b2b2;
|
||||
color: #383838;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border: 1px solid #0096dc;
|
||||
box-shadow: 0 0 5px 0 #0096dc;
|
||||
}
|
||||
|
||||
input[type=password]:focus {
|
||||
color: #2d79c1;
|
||||
}
|
||||
|
||||
input:disabled {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
color: #fff;
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px #333;
|
||||
line-height: 16px;
|
||||
background-color: #aa1401;
|
||||
background-image: -webkit-gradient(linear, left top, left bottom, from(rgba(218, 81, 50, 0.9)), to(rgba(169, 19, 0, 1)));
|
||||
background-image: -webkit-linear-gradient(top, rgba(218, 81, 50, 0.9), rgba(169, 19, 0, 1));
|
||||
background-image: -moz-linear-gradient(top, rgba(218, 81, 50, 0.9), rgba(169, 19, 0, 1));
|
||||
background-image: -ms-linear-gradient(top, rgba(218, 81, 50, 0.9), rgba(169, 19, 0, 1));
|
||||
background-image: -o-linear-gradient(top, rgba(218, 81, 50, 0.9), rgba(169, 19, 0, 1));
|
||||
background-image: linear-gradient(top, rgba(218, 81, 50, 0.9), rgba(169, 19, 0, 1));
|
||||
border-radius: 4px;
|
||||
padding: 10px 15px;
|
||||
box-shadow: 0 1px #666;
|
||||
|
||||
display: none;
|
||||
max-width: 275px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.tooltip .arrow-down {
|
||||
/* This is an awesome CSS only arrow! */
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid #aa1401;
|
||||
border-top: 10px solid rgba(169, 19, 0, 1);
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
|
||||
/* === Hey brother */
|
||||
@media only screen and (max-width: 345px) {
|
||||
body {
|
||||
max-width: 100%;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
.signin-panel {
|
||||
border: solid 0px #ff0000;
|
||||
margin: 0 auto;
|
||||
width: 95%;
|
||||
padding: 20px;
|
||||
}
|
||||
html[dir=rtl] .signin-panel {
|
||||
margin-left: 1em;
|
||||
}
|
||||
.right-col {
|
||||
width: 1em;
|
||||
}
|
||||
.left-col {
|
||||
clear: auto;
|
||||
text-align: left;
|
||||
padding-left: 20px;
|
||||
}
|
||||
html[dir=rtl] .left-col {
|
||||
clear: none;
|
||||
text-align: right;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.left-col,
|
||||
.right-col {
|
||||
border: solid 0px #0000ff;
|
||||
min-width: 400px;
|
||||
max-width: 400px;
|
||||
min-height: 1px;
|
||||
overflow: hidden;
|
||||
float: none;
|
||||
}
|
||||
html[dir=rtl] .left-col,
|
||||
html[dir=rtl] .right-col,
|
||||
html[dir=rtl] .center-col {
|
||||
float: none;
|
||||
}
|
||||
.center-col {
|
||||
border: auto;
|
||||
width: 100%;
|
||||
float: none;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
button {
|
||||
height: 40px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 9px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 275px) {
|
||||
/**
|
||||
* At this width, the buttons start to overlay each other. Put them each on
|
||||
* their own line and let them expand to the full width.
|
||||
*/
|
||||
button, button.cancel {
|
||||
float: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button.cancel {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give a little border to make the buttons look more balanced
|
||||
*/
|
||||
.center-col.last {
|
||||
border-top: 1px dashed #ddd;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.error {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.error a {
|
||||
color: #000;
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
PRELIMINARY DNT POLICY
|
||||
|
||||
This domain interprets DNT as a request for an opt out of collection and
|
||||
retention of visitors' reading habits, which we will respect, subject to
|
||||
reasonable exceptions that respect user privacy.
|
||||
|
||||
This is a temporary document. A full DNT Policy is being drafted at
|
||||
https://eff.org/dnt-policy . When that document is finished, this domain may
|
||||
decide to adopt it.
|
||||
|
||||
You can agree to this same policy by posting it at
|
||||
https://subdomain.example.com/.well-known/dnt-policy.txt, where "subdomain" is
|
||||
any domain to which the policy applies. Commonly it will be posted on third
|
||||
party domains. HTTPS is required.
|
||||
|
Двоичные данные
static/i/mozilla-wordmark-200.png
До Ширина: | Высота: | Размер: 3.6 KiB |
Двоичные данные
static/i/mozilla200x50.png
До Ширина: | Высота: | Размер: 5.1 KiB |
Двоичные данные
static/i/mozilla200x50_purple.png
До Ширина: | Высота: | Размер: 1.4 KiB |
Двоичные данные
static/i/mozilla200x50_red.png
До Ширина: | Высота: | Размер: 1.5 KiB |
Двоичные данные
static/i/mozilla_corp.jpeg
До Ширина: | Высота: | Размер: 8.4 KiB |
Двоичные данные
static/i/vinz_clortho_crop.jpg
До Ширина: | Высота: | Размер: 50 KiB |
|
@ -1,31 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
(function () {
|
||||
// Note: we explicitly ignore persona suggested certificate duration
|
||||
// and issue short-lived certs. Because re-provisioning is
|
||||
// user invisible, this allows us a means of remote session
|
||||
// termination in case of security breaches.
|
||||
navigator.id.beginProvisioning(function(email /* , cert_duration */) {
|
||||
navigator.id.genKeyPair(function(pubkey) {
|
||||
$.ajax({
|
||||
url: '/api/provision',
|
||||
data: JSON.stringify({
|
||||
pubkey: pubkey,
|
||||
user: email,
|
||||
"_csrf": $('[name=_csrf]').val()
|
||||
}),
|
||||
type: 'POST',
|
||||
headers: { "Content-Type": 'application/json' },
|
||||
dataType: 'json',
|
||||
success: function(r) {
|
||||
navigator.id.registerCertificate(r.cert);
|
||||
},
|
||||
error: function(r) {
|
||||
navigator.id.raiseProvisioningFailure("could not provision user");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -1,108 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
$(document).ready(function() {
|
||||
|
||||
setTimeout(function() {
|
||||
$('[name=user]').attr('disabled', 'true');
|
||||
}, 300);
|
||||
|
||||
// XXX: currently the server is plucking email out of get data when
|
||||
// the auth page is rendered. It does this to render to the user
|
||||
// their substituted email address.
|
||||
//
|
||||
// We need the client to bounce the email off the server and sub it in.
|
||||
|
||||
navigator.id.beginAuthentication(function(email) {
|
||||
var msg;
|
||||
|
||||
// all cancel buttons work the same
|
||||
$("form button.cancel").click(function(e) {
|
||||
e.preventDefault();
|
||||
msg = "user canceled authentication";
|
||||
navigator.id.raiseAuthenticationFailure(msg);
|
||||
});
|
||||
|
||||
$("form").submit(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var pass = $.trim($("#pass").val()),
|
||||
auth_url = $('form').attr('action');
|
||||
|
||||
// validate password client side
|
||||
if (pass.length < 6) {
|
||||
showTooltip("Yikes, Passwords have to be at least 6 characters",
|
||||
"#pass");
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: auth_url,
|
||||
type: 'POST',
|
||||
data: { user: email, pass: pass, "_csrf": $('[name=_csrf]').val() },
|
||||
success: function() {
|
||||
navigator.id.completeAuthentication();
|
||||
},
|
||||
error: function() {
|
||||
showTooltip("The account cannot be logged in with this username and password", "#user");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// From here below is tooltip code
|
||||
|
||||
var ANIMATION_TIME = 250,
|
||||
TOOLTIP_MIN_DISPLAY = 2000,
|
||||
TOOLTIP_OFFSET_TOP_PX = 5,
|
||||
TOOLTIP_OFFSET_LEFT_PX = 10,
|
||||
READ_WPM = 200,
|
||||
hideTimer;
|
||||
|
||||
function showTooltip(msg, anchor) {
|
||||
if (hideTimer) {
|
||||
clearTimeout(hideTimer);
|
||||
hideTimer = null;
|
||||
}
|
||||
|
||||
$(".tooltip").hide();
|
||||
$(".tooltip .contents").text(msg);
|
||||
$(".tooltip").fadeIn(ANIMATION_TIME);
|
||||
|
||||
anchorTooltip(".tooltip", anchor);
|
||||
|
||||
var displayTimeMS = calculateDisplayTime(msg);
|
||||
|
||||
hideTimeout = setTimeout(function() {
|
||||
hideTimeout = null;
|
||||
$(".tooltip").fadeOut(ANIMATION_TIME);
|
||||
}, displayTimeMS);
|
||||
}
|
||||
|
||||
function anchorTooltip(tooltip, anchor) {
|
||||
tooltip = $(tooltip);
|
||||
anchor = $(anchor);
|
||||
var tooltipOffset = anchor.offset();
|
||||
tooltipOffset.top -= (tooltip.outerHeight() + TOOLTIP_OFFSET_TOP_PX);
|
||||
tooltipOffset.left += TOOLTIP_OFFSET_LEFT_PX;
|
||||
|
||||
tooltip.css(tooltipOffset);
|
||||
}
|
||||
|
||||
function calculateDisplayTime(text) {
|
||||
// Calculate the amount of time a tooltip should display based on the
|
||||
// number of words in the content divided by the number of words an average
|
||||
// person can read per minute.
|
||||
var contents = text.replace(/\s+/, ' ').trim(),
|
||||
words = contents.split(' ').length,
|
||||
// The average person can read ± 250 wpm.
|
||||
wordTimeMS = (words / READ_WPM) * 60 * 1000,
|
||||
displayTimeMS = Math.max(wordTimeMS, TOOLTIP_MIN_DISPLAY);
|
||||
|
||||
return displayTimeMS;
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
245
tests/auth.js
|
@ -1,245 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// tests of authentication
|
||||
|
||||
|
||||
|
||||
const
|
||||
should = require('should'),
|
||||
request = require('request').defaults({jar: require('request').jar()}),
|
||||
util = require('util'),
|
||||
fs = require('fs'),
|
||||
config = require('../server/lib/configuration');
|
||||
|
||||
// let's pre-write an alias file for the purposes of this test
|
||||
|
||||
describe('authentication', function() {
|
||||
var context;
|
||||
|
||||
it('servers should start', function(done) {
|
||||
var testUtil = require('./lib/test-util');
|
||||
testUtil.startServers(function(err, ctx) {
|
||||
should.not.exist(err);
|
||||
context = ctx;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var csrf_token;
|
||||
it('csrf can be fetched via the api', function(done) {
|
||||
request.get({
|
||||
url: util.format('%s/api/session_context', context.mozillaidp.url),
|
||||
json: true
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(body).should.be.a('object');
|
||||
(body.csrf).should.be.a('string');
|
||||
(resp.headers['cache-control']).should.equal('no-cache, max-age=0');
|
||||
csrf_token = body.csrf;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth should fail with incorrect password', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pass: 'testtestwrong',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(401);
|
||||
(body.success).should.equal(false);
|
||||
(body.reason).should.equal('email or password incorrect');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth should fail with malformed email', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(400);
|
||||
(body.success).should.equal(false);
|
||||
(body.reason).should.equal('user: ValidatorError: Invalid email');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth should fail with short password', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pass: '12345',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(400);
|
||||
(body.success).should.equal(false);
|
||||
(body.reason).should.equal('pass: ValidatorError: String is not in range');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth should fail with extra arguments', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token,
|
||||
alias: 'user2+foo@mozilla.com',
|
||||
email: 'user2+bar@mozilla.com'
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(400);
|
||||
(body.success).should.equal(false);
|
||||
(body.reason).should.equal("unsupported parameter: 'alias', 'email'");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth should fail for DISABLED users', function(done) {
|
||||
// change the employeetype for a specific user
|
||||
var user = context.ldap.findUser('user3@mozilla.com');
|
||||
should.exist(user);
|
||||
user.attributes.employeetype = 'DISABLED';
|
||||
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user3@mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
(resp.statusCode).should.equal(401);
|
||||
(body.success).should.equal(false);
|
||||
|
||||
// *always* clean up after yourself when editing the mock LDAP directory
|
||||
// data ... avoids bad things
|
||||
user.attributes.employeetype = 'Tester'
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth should fail for users not found in ldap', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user-fake@mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
(body.success).should.equal(false);
|
||||
(body.reason).should.equal('email not found');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth (@mozilla.com) should succeed when correct', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('aliased (@mozilla.com) user should authenticate', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'alias2@mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token,
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth (@mozillafoundation.org) should succeed when correct', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozillafoundation.org',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// this user lives under the o=org, dc=mozilla
|
||||
it('auth (test_a@mozilla.com) should succeed', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'test_a@mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
// this user lives under the o=com, dc=mozilla
|
||||
it('auth (test_a@mozillafoundation.org) should succeed', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'test_a@mozillafoundation.org',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('aliased (@mozillafoundation.org) user should authenticate', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'alias2@mozillafoundation.org',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token,
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,266 +0,0 @@
|
|||
// vim: set shiftwidth=2
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// tests of certificate signing
|
||||
|
||||
const
|
||||
should = require('should'),
|
||||
request = require('request').defaults({jar: require('request').jar()}),
|
||||
util = require('util'),
|
||||
testUtil = require('./lib/test-util'),
|
||||
crypto = require('../server/lib/crypto'),
|
||||
config = require('../server/lib/configuration'),
|
||||
jwcrypto = require('jwcrypto'),
|
||||
fs = require('fs');
|
||||
|
||||
// load desired algorithms
|
||||
require("jwcrypto/lib/algs/rs");
|
||||
require("jwcrypto/lib/algs/ds");
|
||||
|
||||
describe('certificate signing', function() {
|
||||
var context;
|
||||
|
||||
it('servers should start', function(done) {
|
||||
testUtil.startServers(function(err, ctx) {
|
||||
should.not.exist(err);
|
||||
context = ctx;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var keypair;
|
||||
it('client side keypair generation succeeds', function(done) {
|
||||
jwcrypto.generateKeypair(
|
||||
{algorithm: 'DS', keysize: 128},
|
||||
function(err, kp) {
|
||||
should.not.exist(err);
|
||||
keypair = kp;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var csrf_token;
|
||||
it('token can be fetched via the api', function(done) {
|
||||
request.get({
|
||||
url: util.format('%s/api/session_context', context.mozillaidp.url),
|
||||
json: true
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(body).should.be.a('object');
|
||||
(body.csrf).should.be.a('string');
|
||||
csrf_token = body.csrf;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('signing request fails without proper CSRF', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/provision', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pubkey: keypair.publicKey.serialize()
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
// 403 returned when CSRF is missing
|
||||
(resp.statusCode).should.equal(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('signing request fails without an authenticated session', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/provision', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pubkey: keypair.publicKey.serialize(),
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
// 401 returned when authentication is missing
|
||||
(resp.statusCode).should.equal(401);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('authentication should succeed', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
(resp.headers['cache-control']).should.equal('no-cache, max-age=0');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('signing request succeeds with an authenticated session', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/provision', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pubkey: keypair.publicKey.serialize(),
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
var serverPubKey = jwcrypto.loadPublicKey(crypto.pubKey);
|
||||
jwcrypto.verify(body.cert, serverPubKey, function(err, payload) {
|
||||
should.not.exist(err);
|
||||
(payload.iss).should.equal(config.get('issuer'));
|
||||
// convert units (s -> ms) and add 10 second padding (certificates are issued
|
||||
// 10s in the past to mitigate minor clock skew)
|
||||
(payload.exp - payload.iat).should.equal((config.get('certificate_validity_s') + 10) * 1000);
|
||||
(payload.iss).should.equal('mozilla.personatest.org');
|
||||
(payload.principal.email).should.equal('user2@mozilla.com');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('signing request succeeds for an alias', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/provision', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'alias2@mozilla.com',
|
||||
pubkey: keypair.publicKey.serialize(),
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
|
||||
var serverPubKey = jwcrypto.loadPublicKey(crypto.pubKey);
|
||||
jwcrypto.verify(body.cert, serverPubKey, function(err, payload) {
|
||||
should.not.exist(err);
|
||||
(payload.iss).should.equal(config.get('issuer'));
|
||||
// convert units (s -> ms) and add 10 second padding (certificates are issued
|
||||
// 10s in the past to mitigate minor clock skew)
|
||||
(payload.exp - payload.iat).should.equal((config.get('certificate_validity_s') + 10) * 1000);
|
||||
(payload.iss).should.equal('mozilla.personatest.org');
|
||||
(payload.principal.email).should.equal('alias2@mozilla.com');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('signing request fails if user is missing', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/provision', context.mozillaidp.url),
|
||||
json: {
|
||||
pubkey: keypair.publicKey.serialize(),
|
||||
_csrf: csrf_token
|
||||
},
|
||||
json: true
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(body.success).should.equal(false);
|
||||
(body.reason).should.equal("missing required parameter: 'user'");
|
||||
(resp.statusCode).should.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('signing request fails if pubkey is missing', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/provision', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
_csrf: csrf_token
|
||||
},
|
||||
json: true
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(body.success).should.equal(false);
|
||||
(body.reason).should.equal("missing required parameter: 'pubkey'");
|
||||
(resp.statusCode).should.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('signing fails when extra params are present', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/provision', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pubkey: keypair.publicKey.serialize(),
|
||||
_csrf: csrf_token,
|
||||
bogus: 'param'
|
||||
},
|
||||
json: true
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(body.success).should.equal(false);
|
||||
(body.reason).should.equal("unsupported parameter: 'bogus'");
|
||||
(resp.statusCode).should.equal(400);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('signed fails when employeeType===DISABLED', function(done) {
|
||||
var user = context.ldap.findUser("user2@mozilla.com");
|
||||
user.attributes.employeetype = "DISABLED";
|
||||
request.post({
|
||||
url: util.format('%s/api/provision', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pubkey: keypair.publicKey.serialize(),
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
user.attributes.employeetype = "Tester";
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(401);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('signing request fails when user has changed their password', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
|
||||
var user = context.ldap.findUser('user2@mozilla.com');
|
||||
var oldChangeTime = user.attributes.pwdChangedTime;
|
||||
user.attributes.pwdChangedTime = "CHANGED"
|
||||
|
||||
request.post({
|
||||
url: util.format('%s/api/provision', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pubkey: keypair.publicKey.serialize(),
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(401);
|
||||
(resp.body).should.equal('Password Changed. Reauthentication required.');
|
||||
|
||||
// make sure the session was reset
|
||||
should.exist(resp.headers['set-cookie']);
|
||||
|
||||
// * reset the value ...
|
||||
user.attributes.pwdChangedTime = oldChangeTime;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,69 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// tests of CSRF protection
|
||||
|
||||
const
|
||||
should = require('should'),
|
||||
request = require('request').defaults({jar: require('request').jar()}),
|
||||
util = require('util'),
|
||||
testUtil = require('./lib/test-util');
|
||||
|
||||
|
||||
describe('CSRF checking', function() {
|
||||
var context;
|
||||
|
||||
it('servers should start', function(done) {
|
||||
testUtil.startServers(function(err, ctx) {
|
||||
should.not.exist(err);
|
||||
context = ctx;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth should fail without proper token', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pass: 'testtest',
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(body).should.equal('Forbidden');
|
||||
(resp.statusCode).should.equal(403);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var csrf_token;
|
||||
it('token can be fetched via the api', function(done) {
|
||||
request.get({
|
||||
url: util.format('%s/api/session_context', context.mozillaidp.url),
|
||||
json: true
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(body).should.be.a('object');
|
||||
(body.csrf).should.be.a('string');
|
||||
(resp.headers['cache-control']).should.equal('no-cache, max-age=0');
|
||||
csrf_token = body.csrf;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth should succeed with proper token', function(done) {
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pass: 'testtest',
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(resp.statusCode).should.equal(200);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,92 +0,0 @@
|
|||
const
|
||||
clorthoServer = require('../server/bin/clortho'),
|
||||
should = require('should'),
|
||||
request = require('request').defaults({jar: require('request').jar()}),
|
||||
util = require('util'),
|
||||
config = require('../server/lib/configuration');
|
||||
|
||||
describe('Health Checking', function() {
|
||||
var serverURL = "";
|
||||
|
||||
before(function() {
|
||||
});
|
||||
|
||||
it('the server should startup', function(done) {
|
||||
clorthoServer.startup(function(err, address){
|
||||
should.not.exist(err);
|
||||
serverURL = "http://" + address.address + ":" + address.port;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 200 on node_status', function(done) {
|
||||
request(
|
||||
util.format('%s%s', serverURL, "/node_status"),
|
||||
function(err, resp, body) {
|
||||
resp.statusCode.should.equal(200);
|
||||
body.should.equal("OK");
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 200 on ldap_status OK', function(done) {
|
||||
request(
|
||||
util.format('%s%s', serverURL, "/ldap_status"),
|
||||
function(err, resp, body) {
|
||||
resp.statusCode.should.equal(200);
|
||||
resp.body.should.equal("OK");
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 503 on ldap_status error', function(done) {
|
||||
// fml, side effects, we need this so when attempting to
|
||||
// bind it will look for an ldap server that doesn't exist
|
||||
var ldapurl = config.get("ldap_server_url");
|
||||
config.set('ldap_server_url', 'ldap://127.0.0.1:1');
|
||||
request(
|
||||
util.format('%s%s', serverURL, "/ldap_status"),
|
||||
function(err, resp, body) {
|
||||
resp.statusCode.should.equal(503);
|
||||
config.set('ldap_server_url', ldapurl);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 503 on ldap_status invalid password', function(done) {
|
||||
// fml, side effects, we need this so when attempting to
|
||||
// bind it will look for an ldap server that doesn't exist
|
||||
var tmp = config.get("ldap_bind_password");
|
||||
config.set('ldap_bind_password', 'fakkkkkkkkkkeeeee!');
|
||||
request(
|
||||
util.format('%s%s', serverURL, "/ldap_status"),
|
||||
function(err, resp, body) {
|
||||
resp.statusCode.should.equal(503);
|
||||
resp.body.should.equal("Error: InvalidCredentialsError");
|
||||
config.set('ldap_bind_password', tmp);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should return 503 on ldap_status invalid DN', function(done) {
|
||||
// fml, side effects, we need this so when attempting to
|
||||
// bind it will look for an ldap server that doesn't exist
|
||||
var tmp = config.get("ldap_bind_dn");
|
||||
config.set('ldap_bind_dn', 'o=notin,com=thetree');
|
||||
request(
|
||||
util.format('%s%s', serverURL, "/ldap_status"),
|
||||
function(err, resp, body) {
|
||||
resp.statusCode.should.equal(503);
|
||||
resp.body.should.equal("Error: NoSuchObjectError");
|
||||
config.set('ldap_bind_dn', tmp);
|
||||
done();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// jshinting (syntax checking) of the source
|
||||
|
||||
const
|
||||
should = require('should'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
jshint = require('jshint').JSHINT,
|
||||
walk = require('walk');
|
||||
|
||||
function jshintFormatter(errors) {
|
||||
return errors.map(function(e) {
|
||||
return e.error.reason + ' ' + e.file + ':' + e.error.line;
|
||||
});
|
||||
}
|
||||
|
||||
describe('source code syntax', function() {
|
||||
// read jshintrc
|
||||
var jshintrc;
|
||||
|
||||
it('.jshintrc should be readable', function(done) {
|
||||
jshintrc = JSON.parse(fs.readFileSync(path.join(__dirname, '../.jshintrc')).toString());
|
||||
(jshintrc).should.be.a('object');
|
||||
done();
|
||||
});
|
||||
|
||||
var filesToLint = [
|
||||
path.join(__dirname, '../server/bin/clortho'),
|
||||
path.join(__dirname, '../server/routes.js')
|
||||
];
|
||||
|
||||
it('we should be able to discover files to lint', function(done) {
|
||||
var walker = walk.walkSync(path.join(__dirname, '../server/lib'), {});
|
||||
|
||||
walker.on("file", function(root, fStat, next) {
|
||||
var f = path.join(root, fStat.name);
|
||||
if (/\.js$/.test(f)) {
|
||||
filesToLint.push(f);
|
||||
}
|
||||
next();
|
||||
});
|
||||
walker.on("end", done);
|
||||
});
|
||||
|
||||
it('syntax checking should yield no errors', function(done) {
|
||||
var errors = [];
|
||||
|
||||
function checkNext() {
|
||||
if (!filesToLint.length) {
|
||||
if (errors.length) {
|
||||
var buf = util.format("\n %d errors:\n * ",
|
||||
errors.length);
|
||||
buf += errors.join("\n * ");
|
||||
done(buf);
|
||||
} else {
|
||||
done(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var f = filesToLint.shift();
|
||||
fs.readFile(f.toString(), function(err, data) {
|
||||
// now
|
||||
f = path.relative(process.cwd(), f);
|
||||
if (!jshint(data.toString(), jshintrc)) {
|
||||
jshint.errors.forEach(function(e) {
|
||||
errors.push(util.format("%s %s:%d - %s", e.id, f, e.line, e.reason));
|
||||
});
|
||||
}
|
||||
checkNext();
|
||||
});
|
||||
}
|
||||
checkNext();
|
||||
});
|
||||
});
|
|
@ -1,79 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Tests of our ldap abstraction against a local node.js LDAP server
|
||||
|
||||
const
|
||||
should = require('should'),
|
||||
ldapMock = require('../server/lib/ldapMock')(),
|
||||
ldap = require('ldapjs'),
|
||||
auth = require('../server/lib/auth.js'),
|
||||
testUtil = require('./lib/test-util.js');
|
||||
|
||||
var ldapServerInstance = null;
|
||||
|
||||
describe('the LDAP authentication library', function() {
|
||||
before(function(done) {
|
||||
ldapServerInstance = ldapMock.server;
|
||||
ldapServerInstance.listen(65077, '127.0.0.1', done);
|
||||
});
|
||||
|
||||
it('should throw when required parameters are missing', function(done) {
|
||||
(function() {
|
||||
auth.authUser({
|
||||
url: 'ldap://127.0.0.1:777',
|
||||
pass: 'secret'
|
||||
}, null);
|
||||
}).should.throw("missing required parameters: dn");
|
||||
done();
|
||||
});
|
||||
|
||||
it('should fail with an unbound port', function(done) {
|
||||
auth.authUser({
|
||||
url: 'ldap://127.0.0.1:777',
|
||||
dn: 'o=example',
|
||||
pass: 'secret'
|
||||
}, function(err) {
|
||||
should.exist(err);
|
||||
(err.message).should.equal("connect ECONNREFUSED");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with a bogus ip', function(done) {
|
||||
auth.authUser({
|
||||
url: 'ldap://192.192.192.192:777',
|
||||
dn: 'o=example',
|
||||
pass: 'secret',
|
||||
connectTimeout: 30 // ms
|
||||
}, function(err) {
|
||||
should.exist(err);
|
||||
(err).should.equal("connect failed");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail with incorrect username', function(done) {
|
||||
auth.authUser({
|
||||
url: ldapServerInstance.url,
|
||||
dn: 'o=badexample',
|
||||
pass: 'secret'
|
||||
}, function(err) {
|
||||
should.exist(err);
|
||||
(err.message).should.equal("No tree found for: o=badexample");
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should succeed when params are correct', function(done) {
|
||||
auth.authUser({
|
||||
url: ldapServerInstance.url,
|
||||
dn: 'mail=user1@mozilla.com, o=com, dc=mozilla',
|
||||
pass: 'testtest'
|
||||
}, function(err) {
|
||||
should.not.exist(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,54 +0,0 @@
|
|||
// vim: set shiftwidth=2
|
||||
/* a home for utilities common to all tests. */
|
||||
|
||||
// disable logging for tests when VERBOSE isn't defined in the env
|
||||
if (!process.env['VERBOSE']) {
|
||||
(require('../../server/lib/logging.js')).disable();
|
||||
}
|
||||
|
||||
const
|
||||
clorthoServer = require('../../server/bin/clortho'),
|
||||
config = require('../../server/lib/configuration'),
|
||||
ldapMock = require('../../server/lib/ldapMock')();
|
||||
|
||||
// nothing to export right now!
|
||||
module.exports = {
|
||||
// start local LDAP server and mozilla IdP bound to ephemeral ports
|
||||
startServers: function(cb) {
|
||||
var ctx = {
|
||||
mozillaidp: {
|
||||
url: ''
|
||||
},
|
||||
ldap: {
|
||||
url: '',
|
||||
instance: null,
|
||||
directory: ldapMock.directory,
|
||||
findUser: function(email) {
|
||||
for(var i=0; i < ldapMock.directory.length; i++) {
|
||||
if (ldapMock.directory[i].attributes.mail == email) {
|
||||
return ldapMock.directory[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
// set configuration for ldap server
|
||||
config.set('ldap_bind_dn', 'mail=user1@mozilla.com, o=com, dc=mozilla');
|
||||
config.set('ldap_bind_password', 'testtest');
|
||||
ctx.ldap.instance = ldapMock.server;
|
||||
ctx.ldap.instance.listen(0, '127.0.0.1', function(err, x) {
|
||||
if (err) return cb(err);
|
||||
ctx.ldap.url = ctx.ldap.instance.url;
|
||||
config.set('ldap_server_url', ctx.ldap.url);
|
||||
clorthoServer.startup(function(err, address) {
|
||||
if (err) return cb(err);
|
||||
ctx.mozillaidp.url = util.format('http://%s:%s',
|
||||
address.address,
|
||||
address.port);
|
||||
cb(null, ctx);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
46
tests/p3p.js
|
@ -1,46 +0,0 @@
|
|||
const
|
||||
should = require('should'),
|
||||
request = require('request').defaults({jar: require('request').jar()}),
|
||||
util = require('util'),
|
||||
fs = require('fs'),
|
||||
config = require('../server/lib/configuration');
|
||||
|
||||
// let's pre-write an alias file for the purposes of this test
|
||||
|
||||
describe('P3P Header', function() {
|
||||
var context;
|
||||
|
||||
it('servers should start', function(done) {
|
||||
var testUtil = require('./lib/test-util');
|
||||
testUtil.startServers(function(err, ctx) {
|
||||
should.not.exist(err);
|
||||
context = ctx;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('P3P headers should be set when IE User-Agent is sent', function(done) {
|
||||
request.get({
|
||||
url: util.format('%s/api/session_context', context.mozillaidp.url),
|
||||
json: true,
|
||||
headers: {
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)'
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
should.exist(resp.headers['p3p'])
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('P3P headers should NOT set by default', function(done) {
|
||||
request.get({
|
||||
url: util.format('%s/api/session_context', context.mozillaidp.url),
|
||||
json: true,
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
should.not.exist(resp.headers['p3p'])
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// tests of crypto routines of the server
|
||||
|
||||
const
|
||||
should = require('should'),
|
||||
testUtil = require('./lib/test-util'),
|
||||
request = require('request').defaults({jar: require('request').jar()}),
|
||||
jwcrypto = require('jwcrypto');
|
||||
|
||||
// load desired algorithms
|
||||
require("jwcrypto/lib/algs/rs");
|
||||
require("jwcrypto/lib/algs/ds");
|
||||
|
||||
var serverURL;
|
||||
|
||||
describe('the public key', function() {
|
||||
it('servers should start', function(done) {
|
||||
testUtil.startServers(function(err, ctx) {
|
||||
should.not.exist(err);
|
||||
serverURL = ctx.mozillaidp.url;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('.well-known/browserid should be properly formatted', function(done) {
|
||||
request(
|
||||
util.format('%s/.well-known/browserid', serverURL),
|
||||
function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
should.exist(body);
|
||||
var parsed = JSON.parse(body);
|
||||
should.exist(parsed['public-key']);
|
||||
// now let's parse the public key
|
||||
var key;
|
||||
(function() {
|
||||
key = jwcrypto.loadPublicKey(JSON.stringify(parsed['public-key']));
|
||||
}).should.not.throw();
|
||||
should.exist(key);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
103
tests/static.js
|
@ -1,103 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// tests of static file serving
|
||||
|
||||
const
|
||||
should = require('should'),
|
||||
testUtil = require('./lib/test-util'),
|
||||
request = require('request').defaults({jar: require('request').jar()}),
|
||||
util = require('util'),
|
||||
config = require('../server/lib/configuration');
|
||||
|
||||
// explicitly disable development mode to test STS headers
|
||||
config.set('local_development', false);
|
||||
|
||||
var securityPolicyValue = util.format("default-src 'self' %s",
|
||||
config.get('browserid_server'));
|
||||
var serverURL;
|
||||
|
||||
describe('static file serving', function() {
|
||||
it('the server should startup', function(done) {
|
||||
testUtil.startServers(function(err, context) {
|
||||
should.not.exist(err);
|
||||
serverURL = context.mozillaidp.url;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var signInPath, provisioningPath;
|
||||
|
||||
it('should respond to requests on .well-known/browserid', function(done) {
|
||||
request(
|
||||
util.format('%s/.well-known/browserid', serverURL),
|
||||
function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
should.exist(body);
|
||||
var parsed = JSON.parse(body);
|
||||
// content type is important, part of the protocol
|
||||
(resp.headers['content-type']).should.equal('application/json');
|
||||
|
||||
// cache headers are important, controls how long verifiers can cache the public key
|
||||
(resp.headers['cache-control']).should.equal('max-age=5, public');
|
||||
|
||||
// we use STS everywhere
|
||||
(resp.headers['strict-transport-security']).should.equal("max-age=10886400; includeSubdomains");
|
||||
|
||||
should.exist(parsed.authentication);
|
||||
signInPath = parsed.authentication
|
||||
should.exist(parsed.provisioning);
|
||||
provisioningPath = parsed.provisioning;
|
||||
should.exist(parsed['public-key']);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should serve provisioning page', function(done) {
|
||||
request(
|
||||
util.format('%s%s', serverURL, provisioningPath),
|
||||
function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
should.exist(body);
|
||||
(resp.headers['content-type']).should.equal('text/html; charset=utf-8');
|
||||
(resp.headers['strict-transport-security']).should.equal("max-age=10886400; includeSubdomains");
|
||||
(resp.headers['x-content-security-policy']).should.equal(
|
||||
securityPolicyValue);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should serve authenticate page', function(done) {
|
||||
request(
|
||||
util.format('%s%s', serverURL, signInPath),
|
||||
function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
should.exist(body);
|
||||
(resp.headers['content-type']).should.equal('text/html; charset=utf-8');
|
||||
(resp.headers['x-frame-options']).should.equal('DENY');
|
||||
(resp.headers['strict-transport-security']).should.equal("max-age=10886400; includeSubdomains");
|
||||
(resp.headers['x-content-security-policy']).should.equal(
|
||||
securityPolicyValue);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should serve 404 page', function(done) {
|
||||
request(
|
||||
util.format('%s%s', serverURL, "/not_found"),
|
||||
function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
should.exist(body);
|
||||
(resp.statusCode).should.equal(404);
|
||||
(resp.headers['content-type']).should.equal('text/html; charset=utf-8');
|
||||
(resp.headers['x-frame-options']).should.equal('DENY');
|
||||
(resp.headers['strict-transport-security']).should.equal("max-age=10886400; includeSubdomains");
|
||||
(resp.headers['x-content-security-policy']).should.equal(
|
||||
securityPolicyValue);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,93 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// tests of authentication throttling
|
||||
|
||||
const
|
||||
should = require('should'),
|
||||
request = require('request').defaults({jar: require('request').jar()}),
|
||||
util = require('util'),
|
||||
testUtil = require('./lib/test-util'),
|
||||
config = require('../server/lib/configuration');
|
||||
|
||||
describe('authentication throttling', function() {
|
||||
var context;
|
||||
function authNTimes(n, password, cb, _lastErr) {
|
||||
if (n === 0) return cb(_lastErr);
|
||||
request.post({
|
||||
url: util.format('%s/api/sign_in', context.mozillaidp.url),
|
||||
json: {
|
||||
user: 'user2@mozilla.com',
|
||||
pass: password,
|
||||
_csrf: csrf_token
|
||||
}
|
||||
}, function(err, resp, body) {
|
||||
var curErr = resp.statusCode === 200 ? null : 'failed';
|
||||
authNTimes(--n, password, cb, err || curErr);
|
||||
});
|
||||
}
|
||||
|
||||
it('servers startup as expected', function(done) {
|
||||
testUtil.startServers(function(err, ctx) {
|
||||
should.not.exist(err);
|
||||
context = ctx;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
var csrf_token;
|
||||
it('a CSRF token can be fetched', function(done) {
|
||||
request.get({
|
||||
url: util.format('%s/api/session_context', context.mozillaidp.url),
|
||||
json: true
|
||||
}, function(err, resp, body) {
|
||||
should.not.exist(err);
|
||||
(body).should.be.a('object');
|
||||
(body.csrf).should.be.a('string');
|
||||
csrf_token = body.csrf;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth will succeed 5 consecutive times', function(done) {
|
||||
authNTimes(5, "testtest", done);
|
||||
});
|
||||
|
||||
it('auth may fail 4 consecutive times', function(done) {
|
||||
authNTimes(4, "badpassword", function(err) {
|
||||
(err).should.equal('failed');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('auth should succeed on the fifth', function(done) {
|
||||
authNTimes(1, "testtest", done);
|
||||
});
|
||||
|
||||
it('auth may fail 5 consecutive times', function(done) {
|
||||
authNTimes(5, "badpassword", function(err) {
|
||||
(err).should.equal('failed');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('which results in a locked out user', function(done) {
|
||||
authNTimes(1, "testtest", function(err) {
|
||||
done(err === 'failed' ? null : "failed to throttle");
|
||||
});
|
||||
});
|
||||
|
||||
it('user is locked out with a bad password', function(done) {
|
||||
authNTimes(1, "bad password", function(err) {
|
||||
done(err === 'failed' ? null : "failed to throttle");
|
||||
});
|
||||
});
|
||||
|
||||
it('user is able to login after auth_lockout_ms expires', function(done) {
|
||||
config.set('auth_lockout_ms', 10);
|
||||
setTimeout(function() {
|
||||
authNTimes(1, "testtest", done);
|
||||
}, 20);
|
||||
});
|
||||
});
|