Compare commits

...

384 Commits

Author SHA1 Message Date
5265064b10 new release: 0.9.0-pre1 2020-06-08 15:47:22 +02:00
ee8313142f Merge branch 'docs-flows'
# Conflicts:
#	passbook/core/templates/partials/form_horizontal.html
2020-06-08 15:43:46 +02:00
45dd004d00 build(deps): bump boto3 from 1.13.23 to 1.13.24 (#55)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.23 to 1.13.24.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.23...1.13.24)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-08 10:16:49 +02:00
c4ebc5c085 build(deps): bump swagger-spec-validator from 2.6.0 to 2.7.0 (#56)
Bumps [swagger-spec-validator](https://github.com/Yelp/swagger_spec_validator) from 2.6.0 to 2.7.0.
- [Release notes](https://github.com/Yelp/swagger_spec_validator/releases)
- [Changelog](https://github.com/Yelp/swagger_spec_validator/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/Yelp/swagger_spec_validator/compare/v2.6.0...v2.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-08 10:04:42 +02:00
31c42f1104 build(deps): bump django-filter from 2.2.0 to 2.3.0 (#54)
Bumps [django-filter](https://github.com/carltongibson/django-filter) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/carltongibson/django-filter/releases)
- [Changelog](https://github.com/carltongibson/django-filter/blob/master/CHANGES.rst)
- [Commits](https://github.com/carltongibson/django-filter/compare/2.2.0...2.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-08 09:02:34 +02:00
8b7f665c82 build(deps): bump django-guardian from 2.2.0 to 2.3.0 (#57)
Bumps [django-guardian](https://github.com/django-guardian/django-guardian) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/django-guardian/django-guardian/releases)
- [Changelog](https://github.com/django-guardian/django-guardian/blob/devel/CHANGES)
- [Commits](https://github.com/django-guardian/django-guardian/compare/v2.2.0...v2.3.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-08 09:01:33 +02:00
4915205678 WIP Use Flows for Sources and Providers (#32)
* core: start migrating to flows for authorisation

* sources/oauth: start type-hinting

* core: create default user

* core: only show user delete button if an unenrollment flow exists

* flows: Correctly check initial policies on flow with context

* policies: add more verbosity to engine

* sources/oauth: migrate to flows

* sources/oauth: fix typing errors

* flows: add more tests

* sources/oauth: start implementing unittests

* sources/ldap: add option to disable user sync, move connection init to model

* sources/ldap: re-add default PropertyMappings

* providers/saml: re-add default PropertyMappings

* admin: fix missing stage count

* stages/identification: fix sources not being shown

* crypto: fix being unable to save with private key

* crypto: re-add default self-signed keypair

* policies: rewrite cache_key to prevent wrong cache

* sources/saml: migrate to flows for auth and enrollment

* stages/consent: add new stage

* admin: fix PropertyMapping widget not rendering properly

* core: provider.authorization_flow is mandatory

* flows: add support for "autosubmit" attribute on form

* flows: add InMemoryStage for dynamic stages

* flows: optionally allow empty flows from FlowPlanner

* providers/saml: update to authorization_flow

* sources/*: fix flow executor URL

* flows: fix pylint error

* flows: wrap responses in JSON object to easily handle redirects

* flow: dont cache plan's context

* providers/oauth: rewrite OAuth2 Provider to use flows

* providers/*: update docstrings of models

* core: fix forms not passing help_text through safe

* flows: fix HttpResponses not being converted to JSON

* providers/oidc: rewrite to use flows

* flows: fix linting
2020-06-07 16:35:08 +02:00
07da5f7122 Merge branch 'master' into docs-flows 2020-06-05 20:30:59 +02:00
f91e02a0ec flows: allow username for default flow 2020-06-05 20:30:47 +02:00
c62794c738 admin: fix PropertyMapping widget not rendering properly 2020-06-05 20:27:18 +02:00
7664b428e7 sources/ldap: fix expression field not being CodeMirror 2020-06-05 20:18:45 +02:00
9f3f34389e root: update docs URL 2020-06-05 20:18:26 +02:00
30ca926b38 docs: remove last occurrences to jinja2 2020-06-05 20:18:11 +02:00
eeb62f543f docs: fix wrong paths in nav section 2020-06-05 17:31:41 +02:00
6a7ffa855e docs: add example for login flow 2020-06-05 17:29:08 +02:00
0c5aff21bc stages/identification: minor UI fixes 2020-06-05 17:02:50 +02:00
b0f426e51a core: fix help_text not always being shown on horizontal form 2020-06-05 17:01:46 +02:00
ed0094eba2 docs: add runtime 2020-06-05 15:58:10 +02:00
4c1b6d5f48 docs: add requirements for docs 2020-06-05 15:54:59 +02:00
41a7e1ef07 docs: simplify index, update, add screenshots 2020-06-05 12:51:28 +02:00
4ff41be74a Merge branch 'master' into docs-flows
# Conflicts:
#	mkdocs.yml
2020-06-05 12:51:10 +02:00
7067d1f236 docs: fix typo'd URL 2020-06-05 12:10:28 +02:00
73116b9d1a policies/expression: migrate to raw python instead of jinja2 (#49)
* policies/expression: migrate to raw python instead of jinja2

* lib/expression: create base evaluator, custom subclass for policies

* core: rewrite propertymappings to use python

* providers/saml: update to new PropertyMappings

* sources/ldap: update to new PropertyMappings

* docs: update docs for new propertymappings

* root: remove jinja2

* root: re-add jinja to lock file as its implicitly required
2020-06-05 12:00:27 +02:00
147212a5f9 build(deps): bump boto3 from 1.13.22 to 1.13.23 (#53)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.22 to 1.13.23.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.22...1.13.23)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-05 10:27:07 +02:00
5502c319d4 build(deps): bump celery from 4.4.2 to 4.4.4 (#50)
Bumps [celery](https://github.com/celery/celery) from 4.4.2 to 4.4.4.
- [Release notes](https://github.com/celery/celery/releases)
- [Changelog](https://github.com/celery/celery/blob/master/Changelog.rst)
- [Commits](https://github.com/celery/celery/compare/4.4.2...v4.4.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 09:15:42 +02:00
d18b76a47d build(deps): bump boto3 from 1.13.21 to 1.13.22 (#51)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.21 to 1.13.22.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.21...1.13.22)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 09:15:31 +02:00
90ce704def build(deps): bump django from 3.0.6 to 3.0.7 (#52)
Bumps [django](https://github.com/django/django) from 3.0.6 to 3.0.7.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.0.6...3.0.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-04 09:15:11 +02:00
790139f8bc stages/email: Add test to check if user is pending 2020-06-03 21:00:04 +02:00
f4be007803 build(deps): bump boto3 from 1.13.20 to 1.13.21 (#47)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.20 to 1.13.21.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.20...1.13.21)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-03 08:56:13 +02:00
448ca62661 build(deps): bump kombu from 4.6.9 to 4.6.10 (#48)
Bumps [kombu](https://kombu.readthedocs.io) from 4.6.9 to 4.6.10.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-03 08:55:23 +02:00
4824e5c8ba docs: add docs for flows and all stages 2020-06-02 23:52:02 +02:00
c4facd53b4 Merge branch 'master' into docs-flows 2020-06-02 20:25:43 +02:00
ef913abc7a sources/ldap: add option to disable user sync, move connection init to model 2020-06-02 17:15:59 +02:00
55fc5a6068 policies: rewrite cache_key to prevent wrong cache
# Conflicts:
#	passbook/core/signals.py
#	passbook/policies/engine.py
#	passbook/policies/process.py
2020-06-02 17:06:18 +02:00
f22c89c998 crypto: re-add default self-signed keypair 2020-06-02 17:01:15 +02:00
2a78d2d0a0 crypto: fix being unable to save with private key 2020-06-02 17:01:15 +02:00
b0ddc6a8c0 admin: fix missing stage count 2020-06-02 17:00:26 +02:00
8080b0380e providers/saml: re-add default PropertyMappings 2020-06-02 17:00:03 +02:00
ddfa2abbaa sources/ldap: re-add default PropertyMappings 2020-06-02 17:00:03 +02:00
5fc5e54f47 sources/oauth: fix typing errors
# Conflicts:
#	passbook/sources/oauth/clients.py
2020-06-02 16:57:38 +02:00
7462d56182 policies/engine: add more verbosity 2020-06-02 16:56:58 +02:00
3f92d1c420 flows: Correctly check initial policies on flow with context
# Conflicts:
#	passbook/flows/planner.py
#	passbook/flows/tests/test_planner.py
#	passbook/flows/tests/test_views.py
#	passbook/flows/views.py
2020-06-02 16:53:40 +02:00
d4fa60f509 core: only show user delete button if an unenrollment flow exists 2020-06-02 16:44:29 +02:00
052bf88c3d core: create default user
# Conflicts:
#	README.md
2020-06-02 16:42:44 +02:00
07d047c887 stages/identification: fix *_flows missing in edit form 2020-06-02 16:39:55 +02:00
23193314f1 Merge branch 'master' into docs-flows 2020-06-02 15:20:24 +02:00
1912b29dc5 policies/expression: fix lint error 2020-06-02 15:20:02 +02:00
46410428d9 build(deps): bump boto3 from 1.13.19 to 1.13.20 (#45)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.19 to 1.13.20.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.19...1.13.20)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-02 09:38:17 +02:00
1774e33c24 build(deps): bump kombu from 4.6.8 to 4.6.9 (#46)
Bumps [kombu](https://kombu.readthedocs.io) from 4.6.8 to 4.6.9.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-02 09:37:53 +02:00
c961327d27 stages/identification: fix recovery template 2020-06-01 19:08:14 +02:00
fe1ff7fc76 core: fix form not showing general errors 2020-06-01 19:08:01 +02:00
82d12ecfdf policies/expression: use pb_message() for messages instead of returning a tuple 2020-06-01 15:25:38 +02:00
6ed822fa38 build(deps-dev): bump autopep8 from 1.5.2 to 1.5.3 (#44)
Bumps [autopep8](https://github.com/hhatto/autopep8) from 1.5.2 to 1.5.3.
- [Release notes](https://github.com/hhatto/autopep8/releases)
- [Commits](https://github.com/hhatto/autopep8/compare/v1.5.2...v1.5.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-06-01 12:40:08 +02:00
4d1658b35e stages/identification: explicitly define enrollment and recovery 2020-05-31 23:01:08 +02:00
08302a04bf docs: add docs for flows, start docs for stages 2020-05-30 22:36:01 +02:00
8b4558fcd0 build(deps): bump boto3 from 1.13.18 to 1.13.19 (#41)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.18 to 1.13.19.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.18...1.13.19)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-29 08:53:41 +02:00
80a36a3441 stages/user_write: don't use create_user, create empty object and set attributes 2020-05-28 23:22:15 +02:00
b1b63fbb7f stages/prompt: add prompt order field 2020-05-28 23:21:43 +02:00
fe6bfb1620 stages/identification: fix wrong link 2020-05-28 22:44:42 +02:00
e6d64bf5b3 stages/identification: fix sources and recovery/enrollment not being shown 2020-05-28 22:43:44 +02:00
270739a45a admin: fix policy testing form not showing the correct result 2020-05-28 21:50:43 +02:00
df8995deed policies/*: remove Policy.negate, order, timeout (#39)
policies: rewrite engine to use PolicyBinding for order/negate/timeout
policies: rewrite engine to use PolicyResult instead of tuple
2020-05-28 21:45:54 +02:00
fdfc6472d2 admin: fixup some urls 2020-05-28 10:36:27 +02:00
bc495828e7 build(deps): bump django-redis from 4.11.0 to 4.12.1 (#38)
Bumps [django-redis](https://github.com/jazzband/django-redis) from 4.11.0 to 4.12.1.
- [Release notes](https://github.com/jazzband/django-redis/releases)
- [Changelog](https://github.com/jazzband/django-redis/blob/master/CHANGES.rst)
- [Commits](https://github.com/jazzband/django-redis/compare/4.11.0...4.12.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-28 09:39:28 +02:00
fa138a273f build(deps): bump boto3 from 1.13.17 to 1.13.18 (#37)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.17 to 1.13.18.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.17...1.13.18)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-28 08:59:19 +02:00
5555dd4dde root: update readme 2020-05-27 17:57:43 +02:00
084ff2994f ci: add cleanup to CI to remove old tags 2020-05-27 17:33:27 +02:00
ee8a61d164 root: update readme 2020-05-27 17:26:35 +02:00
60a363e4bf build(deps): bump boto3 from 1.13.15 to 1.13.17 (#35)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.15 to 1.13.17.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.15...1.13.17)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-27 11:53:59 +02:00
e2804b9755 root: fix linting errors 2020-05-27 11:26:48 +02:00
9330a03273 root: fix pylint not correctly ignoring migrations 2020-05-27 11:15:10 +02:00
beabba2890 flows: Load Stages without refreshing the whole page (#33)
* flows: initial implementation of FlowExecutorShell

* flows: load messages dynamically upon card refresh
2020-05-24 00:57:25 +02:00
eeeb14a045 admin: fix urls for applications 2020-05-22 23:22:40 +02:00
ff3f126832 build(deps): bump boto3 from 1.13.14 to 1.13.15 (#31)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.14 to 1.13.15.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.14...1.13.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-22 09:55:25 +02:00
73225917cd build(deps): bump codemirror in /passbook/static/static (#29)
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.53.2 to 5.54.0.
- [Release notes](https://github.com/codemirror/CodeMirror/releases)
- [Changelog](https://github.com/codemirror/CodeMirror/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codemirror/CodeMirror/compare/5.53.2...5.54.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-21 19:12:14 +02:00
8b7a285f4a build(deps): bump swagger-spec-validator from 2.5.0 to 2.6.0 (#28)
Bumps [swagger-spec-validator](https://github.com/Yelp/swagger_spec_validator) from 2.5.0 to 2.6.0.
- [Release notes](https://github.com/Yelp/swagger_spec_validator/releases)
- [Changelog](https://github.com/Yelp/swagger_spec_validator/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/Yelp/swagger_spec_validator/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-21 19:11:27 +02:00
3a4c6a5d67 build(deps): bump boto3 from 1.13.13 to 1.13.14 (#30)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.13 to 1.13.14.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.13...1.13.14)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-21 14:20:51 +02:00
0664f0b6b2 flows: add support for default_context, etc
default_context can be used to influence policies during the planning. This should be used when the Planner is called from other views to correctly preseed the plan.
This also checks if there is a PENDING_USER set, and uses that user for the cache key instead
2020-05-20 16:15:16 +02:00
fab9f1cb1b root: clear cache on debug startup 2020-05-20 16:13:48 +02:00
b3c54308b7 policies/expression: validate syntax on form clean 2020-05-20 16:13:36 +02:00
c1a72a0474 crypto: fix clean_ methods not returning values 2020-05-20 16:10:12 +02:00
4377d1e86e admin: fix flow ordering, fix source URLs 2020-05-20 16:09:53 +02:00
cafe2f1e1f admin: fix linting 2020-05-20 13:59:56 +02:00
c329a724e8 admin: fix wrong name in template 2020-05-20 13:55:17 +02:00
969da05437 admin: show object's docstring on inheritance based lists 2020-05-20 13:47:58 +02:00
c61c84e9f3 admin: remove inline JS, add CodeMirror widget for jinja and yaml 2020-05-20 13:00:45 +02:00
e08c5ff875 helm: fully remove domain, add log_level 2020-05-20 12:16:56 +02:00
d1fd616b8d build(deps): bump packaging from 20.3 to 20.4 (#25)
Bumps [packaging](https://github.com/pypa/packaging) from 20.3 to 20.4.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/20.3...20.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-20 09:41:31 +02:00
cc4bbc049b build(deps): bump boto3 from 1.13.12 to 1.13.13 (#27)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.12 to 1.13.13.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.12...1.13.13)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-20 09:38:04 +02:00
24a3e787dd migrate to per-model UUID Primary key, remove UUIDModel (#26)
* *: migrate to per-model UUID Primary key, remove UUIDModel

* *: fix import order, fix unittests
2020-05-20 09:17:06 +02:00
13a20478fd sources/oauth: add OIDC client 2020-05-19 21:53:46 +02:00
f58ee7fb52 sources/oauth: fix handling of sources with spaces in their name 2020-05-19 21:53:36 +02:00
4d45dc31a9 helm: limit deployments to their respective components 2020-05-19 21:25:56 +02:00
f6b5fbc2cf admin: fix sidebar 2020-05-19 21:02:10 +02:00
db69c3e38d admin: add generic tests 2020-05-19 20:59:43 +02:00
5ba55356a9 admin: cleanup templates, cleanup templatetags 2020-05-19 20:29:02 +02:00
6104b7c9ba build(deps): bump lxml from 4.5.0 to 4.5.1 (#24)
Bumps [lxml](https://github.com/lxml/lxml) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-4.5.0...lxml-4.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-19 15:58:30 +02:00
bcef8369ff build(deps): bump boto3 from 1.13.11 to 1.13.12 (#23)
Bumps [boto3](https://github.com/boto/boto3) from 1.13.11 to 1.13.12.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.13.11...1.13.12)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-19 07:40:00 +02:00
0fe555a76e Merge pull request #17 from BeryJu/dependabot/pip/pylint-2.5.2
build(deps-dev): bump pylint from 2.4.4 to 2.5.2
2020-05-19 07:33:17 +02:00
c903c81bd5 root: update pylint ignore list 2020-05-18 18:15:39 +02:00
2c43bfb13f Merge pull request #22 from BeryJu/dependabot/pip/urllib3-1.25.9
build(deps): bump urllib3 from 1.25.8 to 1.25.9
2020-05-18 16:04:40 +02:00
24ecab0878 Merge pull request #21 from BeryJu/dependabot/pip/sentry-sdk-0.14.4
build(deps): bump sentry-sdk from 0.14.3 to 0.14.4
2020-05-18 15:53:32 +02:00
6285c79341 Merge pull request #20 from BeryJu/dependabot/pip/django-otp-0.9.1
build(deps): bump django-otp from 0.8.1 to 0.9.1
2020-05-18 15:35:23 +02:00
5f7c56fab4 Merge pull request #19 from BeryJu/dependabot/pip/coverage-5.1
build(deps-dev): bump coverage from 5.0.4 to 5.1
2020-05-18 15:01:29 +02:00
6338b14a5b Merge pull request #18 from BeryJu/dependabot/pip/django-3.0.6
build(deps): bump django from 3.0.5 to 3.0.6
2020-05-18 14:51:44 +02:00
62ff3667f9 build(deps): bump urllib3 from 1.25.8 to 1.25.9
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.25.8 to 1.25.9.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/master/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.25.8...1.25.9)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 12:29:33 +00:00
3b97eef641 build(deps): bump sentry-sdk from 0.14.3 to 0.14.4
Bumps [sentry-sdk](https://github.com/getsentry/sentry-python) from 0.14.3 to 0.14.4.
- [Release notes](https://github.com/getsentry/sentry-python/releases)
- [Changelog](https://github.com/getsentry/sentry-python/blob/master/CHANGES.md)
- [Commits](https://github.com/getsentry/sentry-python/compare/0.14.3...0.14.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 12:28:09 +00:00
b05d66466b build(deps): bump django-otp from 0.8.1 to 0.9.1
Bumps [django-otp](https://github.com/django-otp/django-otp) from 0.8.1 to 0.9.1.
- [Release notes](https://github.com/django-otp/django-otp/releases)
- [Changelog](https://github.com/django-otp/django-otp/blob/master/CHANGES.rst)
- [Commits](https://github.com/django-otp/django-otp/compare/v0.8.1...v0.9.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 12:26:41 +00:00
220144c919 build(deps-dev): bump coverage from 5.0.4 to 5.1
Bumps [coverage](https://github.com/nedbat/coveragepy) from 5.0.4 to 5.1.
- [Release notes](https://github.com/nedbat/coveragepy/releases)
- [Changelog](https://github.com/nedbat/coveragepy/blob/master/CHANGES.rst)
- [Commits](https://github.com/nedbat/coveragepy/compare/coverage-5.0.4...coverage-5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 12:25:16 +00:00
3b9402420d build(deps): bump django from 3.0.5 to 3.0.6
Bumps [django](https://github.com/django/django) from 3.0.5 to 3.0.6.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/3.0.5...3.0.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 12:23:42 +00:00
1324143146 Merge pull request #13 from BeryJu/dependabot/pip/autopep8-1.5.2
build(deps-dev): bump autopep8 from 1.5.1 to 1.5.2
2020-05-18 14:20:01 +02:00
595144cdd9 build(deps-dev): bump pylint from 2.4.4 to 2.5.2
Bumps [pylint](https://github.com/PyCQA/pylint) from 2.4.4 to 2.5.2.
- [Release notes](https://github.com/PyCQA/pylint/releases)
- [Changelog](https://github.com/PyCQA/pylint/blob/master/ChangeLog)
- [Commits](https://github.com/PyCQA/pylint/compare/pylint-2.4.4...pylint-2.5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 12:19:16 +00:00
b3e714a741 Merge pull request #12 from BeryJu/dependabot/npm_and_yarn/passbook/static/static/codemirror-5.53.2
build(deps): bump codemirror from 5.52.0 to 5.53.2 in /passbook/static/static
2020-05-18 14:15:29 +02:00
2b3ae1e2d5 Merge pull request #10 from BeryJu/dependabot/pip/pylint-django-2.0.15
build(deps-dev): bump pylint-django from 2.0.14 to 2.0.15
2020-05-18 14:15:18 +02:00
886845306a build(deps): bump codemirror in /passbook/static/static
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.52.0 to 5.53.2.
- [Release notes](https://github.com/codemirror/CodeMirror/releases)
- [Changelog](https://github.com/codemirror/CodeMirror/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codemirror/CodeMirror/compare/5.52.0...5.53.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 11:50:33 +00:00
de6aaec088 Merge pull request #11 from BeryJu/dependabot/npm_and_yarn/passbook/static/static/fortawesome/fontawesome-free-5.13.0
build(deps): bump @fortawesome/fontawesome-free from 5.12.1 to 5.13.0 in /passbook/static/static
2020-05-18 13:49:21 +02:00
137e0854ab Merge pull request #15 from BeryJu/dependabot/pip/bumpversion-0.6.0
build(deps-dev): bump bumpversion from 0.5.3 to 0.6.0
2020-05-18 13:48:52 +02:00
b6a0e895bc build(deps): bump @fortawesome/fontawesome-free
Bumps [@fortawesome/fontawesome-free](https://github.com/FortAwesome/Font-Awesome) from 5.12.1 to 5.13.0.
- [Release notes](https://github.com/FortAwesome/Font-Awesome/releases)
- [Changelog](https://github.com/FortAwesome/Font-Awesome/blob/master/CHANGELOG.md)
- [Commits](https://github.com/FortAwesome/Font-Awesome/compare/5.12.1...5.13.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 11:03:22 +00:00
05c791b1c0 build(deps-dev): bump bumpversion from 0.5.3 to 0.6.0
Bumps [bumpversion](https://github.com/peritus/bumpversion) from 0.5.3 to 0.6.0.
- [Release notes](https://github.com/peritus/bumpversion/releases)
- [Commits](https://github.com/peritus/bumpversion/commits)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 11:02:36 +00:00
669f3051e8 build(deps-dev): bump pylint-django from 2.0.14 to 2.0.15
Bumps [pylint-django](https://github.com/PyCQA/pylint-django) from 2.0.14 to 2.0.15.
- [Release notes](https://github.com/PyCQA/pylint-django/releases)
- [Changelog](https://github.com/PyCQA/pylint-django/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/PyCQA/pylint-django/compare/v2.0.14...v2.0.15)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 11:02:17 +00:00
d945b56561 Merge pull request #14 from BeryJu/dependabot/npm_and_yarn/passbook/static/static/patternfly/patternfly-2.71.6
build(deps): bump @patternfly/patternfly from 2.65.3 to 2.71.6 in /passbook/static/static
2020-05-18 13:02:08 +02:00
27333bbff1 build(deps-dev): bump autopep8 from 1.5.1 to 1.5.2
Bumps [autopep8](https://github.com/hhatto/autopep8) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/hhatto/autopep8/releases)
- [Commits](https://github.com/hhatto/autopep8/compare/v1.5.1...v1.5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 11:02:07 +00:00
80c800f233 Merge pull request #16 from BeryJu/dependabot/pip/boto3-1.13.11
build(deps): bump boto3 from 1.12.39 to 1.13.11
2020-05-18 12:58:40 +02:00
5d17d3dd31 build(deps): bump boto3 from 1.12.39 to 1.13.11
Bumps [boto3](https://github.com/boto/boto3) from 1.12.39 to 1.13.11.
- [Release notes](https://github.com/boto/boto3/releases)
- [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
- [Commits](https://github.com/boto/boto3/compare/1.12.39...1.13.11)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 09:51:42 +00:00
8c64fcd17f build(deps): bump @patternfly/patternfly in /passbook/static/static
Bumps [@patternfly/patternfly](https://github.com/patternfly/patternfly) from 2.65.3 to 2.71.6.
- [Release notes](https://github.com/patternfly/patternfly/releases)
- [Changelog](https://github.com/patternfly/patternfly/blob/master/RELEASE-NOTES.md)
- [Commits](https://github.com/patternfly/patternfly/compare/v2.65.3...v2.71.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-05-18 09:49:27 +00:00
753110583e admin: fix incorrect URL 2020-05-17 23:55:42 +02:00
4206a8c43e stages/password: fix button without labeling 2020-05-17 23:55:25 +02:00
7cc9ae45bc static: fix any click on menu triggering collapse 2020-05-17 23:55:13 +02:00
09ef58350c flows: rename AuthenticationStage to StageView as its a general base view 2020-05-17 00:03:06 +02:00
0b70007926 docs: add upgrade page 2020-05-16 23:46:25 +02:00
2c5d6e8187 docker: fix import order on wait_for_db 2020-05-16 22:56:14 +02:00
10d3f9ac2f root: rewrite bootstrap in python, remove management command 2020-05-16 22:46:17 +02:00
a6301055f0 root: remove need for PASSBOOK_DOMAIN 2020-05-16 21:47:51 +02:00
9a1b8eb7c8 admin: add empty views to all lists, move invitation to stage-invitations 2020-05-16 21:43:48 +02:00
2ee45f388c crypto: validate PEM data before saving 2020-05-16 21:25:10 +02:00
9a9feea5ff static: make hamburger menu work 2020-05-16 21:11:48 +02:00
8540e787d3 admin: fix create buttons disappearing on small screens 2020-05-16 21:11:38 +02:00
db26ce808e core: remove policies from application form 2020-05-16 20:48:45 +02:00
802d568273 admin: fix urls applications and sources 2020-05-16 20:48:30 +02:00
7b6524357f stages/user_logout: fix dependency on PENDING_USER 2020-05-16 20:46:21 +02:00
83013f063d core: remove stub data from user settings 2020-05-16 20:45:43 +02:00
333ee3eed4 dc: fix compose, remove SESSION_COOKIE_DOMAIN 2020-05-16 20:39:38 +02:00
e68352b09c admin: add flow-stage-bindings, add policy-bindings, add prompts 2020-05-16 19:55:59 +02:00
df1cb88abc admin: fix slow load for templates using get_links 2020-05-16 19:00:43 +02:00
5596caedbc Merge pull request #9 from BeryJu/db-reset
DB Reset
2020-05-16 18:20:03 +02:00
7bd65120b9 *: migrate from PolicyModel to PolicyBindingModel, move Policy to passbook_policies 2020-05-16 18:07:00 +02:00
227966e727 core: rename nonce to token 2020-05-16 16:11:53 +02:00
406f69080b Revert "*: providers and sources -> channels, PolicyModel to PolicyBindingModel that uses custom M2M through"
This reverts commit 7ed3ceb960.
2020-05-16 16:02:42 +02:00
2ce31dfaa5 root: upgrade django-dbbackup 2020-05-16 14:54:18 +02:00
b23afcfc3b helm: expose backup settings through helm chart 2020-05-16 14:17:43 +02:00
7ed3ceb960 *: providers and sources -> channels, PolicyModel to PolicyBindingModel that uses custom M2M through 2020-05-16 14:03:57 +02:00
615cd7870d stages/email: add field to select E-Mail and subject 2020-05-15 14:50:23 +02:00
b907105f4a policies/expression: expose python requests via expression, remove webhook policy 2020-05-15 12:02:41 +02:00
023423c6e7 stages/password: show password forgotten prompt when recovery flow configured 2020-05-15 11:01:02 +02:00
a5319fc2fe *: rename templatetags to clearly identify 2020-05-15 10:54:31 +02:00
8cfd3f9a2b Merge pull request #8 from BeryJu/flows-stage1
Flows Stage 1
2020-05-14 16:07:22 +02:00
814c797c64 stages/prompt: add policy verification logic 2020-05-14 15:48:23 +02:00
776ad3cfbf policies/expression: add pb_log function to debug 2020-05-14 15:48:08 +02:00
74ddf70cb7 policy: add context to PolicyRequest 2020-05-14 15:47:56 +02:00
a7a839a29c stages/prompt: promptstage based on PolicyBindingModel 2020-05-14 13:51:35 +02:00
9859c5db0a policies: add API for policybindings 2020-05-14 13:51:05 +02:00
fe503c8de0 root: add swagger to repository 2020-05-14 13:45:46 +02:00
43a583e2d2 stages/invitation: add unittests 2020-05-13 23:20:27 +02:00
f289025d8e stages/user_delete: fix missing API, fix missing tests 2020-05-13 23:20:07 +02:00
19cb310446 gh/actions: run coverage xml before codecov 2020-05-13 22:40:09 +02:00
47f6d0ac59 gh/actions: fix invalid path for codecov 2020-05-13 22:27:18 +02:00
922cbf932d : update readme 2020-05-13 19:05:01 +02:00
c104eeebe6 gh/actions: add codecov 2020-05-13 18:52:37 +02:00
80c3246333 policies/expression: add pb_flow_plan variable 2020-05-13 18:44:36 +02:00
461fed5567 admin: remove more duplicate code 2020-05-13 13:45:57 +02:00
d5f6714ed7 admin: remove redundant code 2020-05-13 11:57:19 +02:00
c42ed6bc99 admin: sort types, minor fixups 2020-05-13 11:57:10 +02:00
57fed2b92b stages/user_delete: fix formatting 2020-05-12 15:23:17 +02:00
e45b33c6c2 stages/user_delete: add user delete stage, remove view from core 2020-05-12 14:50:00 +02:00
137e90355b flows: default-auth -> default-authentication 2020-05-12 14:49:47 +02:00
7500e622f6 stages/invitation: start extracting invitation from core 2020-05-11 21:58:02 +02:00
d49c58f326 flows: fix linting 2020-05-11 21:27:46 +02:00
9814d3be03 flows: add Planner and Executor unittests 2020-05-11 15:01:14 +02:00
fc9f86cccc lib: use TemplateResponse for bad_request_message 2020-05-11 14:08:04 +02:00
6fd19c0a37 flows: add caching of plan, add planner unittests 2020-05-11 11:39:58 +02:00
10cb412532 flows: fix linting of migrations 2020-05-11 09:08:15 +02:00
e12780f78f flows: add invalidation designation, use as default logout action 2020-05-11 01:12:57 +02:00
9dec13c225 stages/user_logout: add logout stage 2020-05-11 01:12:14 +02:00
69120da45c core: remove redundant views/forms 2020-05-11 00:49:48 +02:00
5b2bf7519a stages/user_create -> user_write: Stage can create and update existing users 2020-05-10 23:38:15 +02:00
631cf77f89 stages/captcha: add tests 2020-05-10 21:43:30 +02:00
6676e95011 stages/email: add tests, cleanup 2020-05-10 21:43:22 +02:00
3219cffb52 stages/email: add logic to verify token 2020-05-10 21:00:04 +02:00
d4f149bc02 stages/email: add form for sending email to prevent spam
stages/email: make token validity configurable
2020-05-10 20:50:27 +02:00
206cf4967d stages/identification: add more templates 2020-05-10 20:24:47 +02:00
a67c53f46a stages/email: start rewriting templates, add template tags to embed CSS and images 2020-05-10 20:16:58 +02:00
e989c61793 core: reduce default nonce time to 30 minutes 2020-05-10 20:15:56 +02:00
99bab03cce flows: add check if current plan matches current flow 2020-05-10 20:15:24 +02:00
a7567ad8c6 stages/identification: add recovery support 2020-05-10 18:45:16 +02:00
2ffa2fc6b8 admin: update templates and stage views 2020-05-10 18:44:58 +02:00
8de87d9acb stages/identification: test signup notice 2020-05-10 18:17:24 +02:00
1d03b36750 stages/identification: show sign up url when related flow exists 2020-05-10 18:14:10 +02:00
8dc3c49a2f stages/user_create: add stage to create user after prompts 2020-05-10 18:04:23 +02:00
f6461b08d7 stages/prompt: add unittests 2020-05-10 17:52:26 +02:00
a3a3dde1c8 stages/dummy: add unittests
stages/password: improve coverage
stages/user_login: improve coverage
2020-05-10 17:02:01 +02:00
f111604b70 stages/login: -> stages/user_login: rename login to user_login for user_create stage 2020-05-10 16:20:44 +02:00
4315d1a03c stages/prompt: add prompt stage: dynamically created forms based on database 2020-05-10 16:20:17 +02:00
9def45c8d7 stages/identification: fix label for inputs 2020-05-10 15:29:27 +02:00
358922b09b lib/ui: fix human_list for lists with one item 2020-05-10 15:29:10 +02:00
fbc3ac6b30 flows: make FlowExecutor fully working without pending user 2020-05-10 15:28:52 +02:00
4f785da452 root: fix migrations for CI 2020-05-10 13:06:38 +02:00
ff4bd1c91f root: increase testing verbosity to debug CI 2020-05-10 12:50:58 +02:00
7a96f9e894 policies/dummy: fix migrations 2020-05-10 12:27:10 +02:00
c27d257146 core: fix migrations 2020-05-10 12:07:40 +02:00
9bccf9bb0a policies/dummy: separate dummy policy from core into app 2020-05-10 02:14:55 +02:00
c0b05a62f4 stages/password: add unittests 2020-05-10 02:00:38 +02:00
c140c39d07 stages/login: add unittests 2020-05-10 01:02:04 +02:00
9a700e506b stages/identification: simplify unittests 2020-05-10 01:01:58 +02:00
8e488670ad stages/identification: load uid_fields from stage in form, add more unit tests 2020-05-10 00:05:36 +02:00
fd5b2298e5 flows: fix unittests and migrations 2020-05-09 23:31:35 +02:00
caeaf8d5a9 stages/identification: optimise User lookup query 2020-05-09 23:20:20 +02:00
c46f0781fc flows: separate final login step from flow executor 2020-05-09 23:19:36 +02:00
0aad0604d8 stages/identification: migrate from core to separate stage 2020-05-09 21:31:29 +02:00
131c3fdb32 stages/password: fix broken authentication 2020-05-09 21:30:12 +02:00
8a6009c278 flows: enum to django TextChoices 2020-05-09 20:54:56 +02:00
3456527f10 providers/saml: fix minor typing issue 2020-05-09 20:54:11 +02:00
28b913136d root: set log level based on DEBUG flag 2020-05-09 20:53:47 +02:00
f700899640 stages/password: fix possibility of password in logs 2020-05-09 20:53:32 +02:00
5ba45d3037 root: add fossa 2020-05-08 21:08:36 +02:00
212e966dd4 factors: -> stage 2020-05-08 20:59:51 +02:00
08c0eb2ec6 admin: add flows 2020-05-08 18:45:53 +02:00
872ecd93a6 flows: add to api and add forms 2020-05-08 18:29:18 +02:00
f8af9d6ce0 flows: make sure flow_slug is logged consistently 2020-05-08 17:18:43 +02:00
c3e43a7c2f flows: fix denied view not being registered 2020-05-08 16:50:50 +02:00
273af0f1cb core/auth: fix unittests for flows 2020-05-08 16:43:22 +02:00
2a85e5ae87 flows: complete migration to FlowExecutorView, fully use context 2020-05-08 16:10:27 +02:00
114bb1b0bd flows: implement planner, start new executor 2020-05-08 14:33:14 +02:00
97b5d120f8 providers/oauth: fix default cors settings 2020-05-08 11:26:26 +02:00
e1f0fe45cb static: fix dashes being removed from slugs 2020-05-08 11:26:12 +02:00
bc0f4973d8 helm: fix bootstrap not being used correctly 2020-05-08 09:42:21 +02:00
5400882d78 flows/: more migration progress, consolidate views 2020-05-07 21:30:52 +02:00
8de66b27ad flows/*: Initial flows stage1 implementation 2020-05-07 20:51:06 +02:00
179f0097c0 provider/samlv2: more samlv2 progres 2020-05-07 19:25:15 +02:00
b40bffdf38 providers/samlv2: start implementing new SAML Provider 2020-05-07 01:20:08 +02:00
2e9496bb74 gh/ci: run pyright in python venv 2020-05-07 00:38:41 +02:00
d9d765c6cd gh/ci: fix ci syntax 2020-05-07 00:36:13 +02:00
10cc6856a4 gh/ci: fix pyright not being installed 2020-05-07 00:33:42 +02:00
813dd2894f *: add pyright type checking 2020-05-07 00:32:03 +02:00
80d90b91e8 core: add general admin.py loader, remove individual files 2020-05-07 00:05:10 +02:00
fff05e35ac providers/saml: optionally verify SAML Signature 2020-05-06 18:03:12 +02:00
75bb59a22a ui: fix help text not being shown for checkboxes 2020-05-06 17:59:19 +02:00
3ce69bb391 compose: remove explicit bootstrapping 2020-04-22 11:45:43 +02:00
4eb7c5f94b helm: remove explicit bootstrapping 2020-04-22 11:45:36 +02:00
aac7e6be90 lib: fix ram usage due to bootstrap
bootstrap now exits (0) when all services are up, instead continuously running. This is combined with a simple bash script, which does this job instead.

This also adds /bootstrap.sh as docker ENTRYPOINT
2020-04-22 11:45:11 +02:00
c77f4204c0 new release: 0.8.15-beta 2020-04-10 21:57:20 +02:00
5f4452470b providers/saml: fix metadata rendering when no singing keypair is selected
closes PASSBOOK-44
2020-04-10 21:54:23 +02:00
9a1270c693 providers/saml: fix wrong signing property being checked
closes PASSBOOK-45
2020-04-10 21:52:03 +02:00
7b9d1a1159 new release: 0.8.14-beta 2020-04-10 21:23:55 +02:00
cdbe1f6161 bump dependencies 2020-04-10 21:17:31 +02:00
e43db2e065 new release: 0.8.13-beta 2020-04-10 21:11:24 +02:00
d1c74d2160 lib: fix imports being changed every time 2020-03-05 17:28:03 +01:00
f2119ce567 providers/saml: fix signing_kp typo 2020-03-05 17:09:08 +01:00
2c4dcb9cf0 actions: remove cache 2020-03-04 21:15:44 +01:00
93b8266821 actions: install pipenv as root 2020-03-04 21:13:40 +01:00
443797d9b0 actions: install wheel package 2020-03-04 21:11:26 +01:00
a4365ca02c actions: don't update system pip 2020-03-04 21:09:46 +01:00
3750083667 actions: don't setup custom python, use system pip 2020-03-04 21:08:24 +01:00
66ef067ecf actions: don't update pip to fix CI 2020-03-04 20:10:46 +01:00
b489b0e691 Merge pull request #7 from BeryJu/crypto
generic cert management
2020-03-04 19:43:52 +01:00
f2154d9875 crypto: add property for private_key 2020-03-04 19:43:18 +01:00
80a50f9bdb providers/saml: switch to new crypto 2020-03-03 23:35:50 +01:00
dc8b89a6b9 sources/saml: switch to new crypto 2020-03-03 23:35:38 +01:00
8df55f22aa crypto: implement simple certificate-key pair for easier management 2020-03-03 23:35:25 +01:00
f6c322be27 providers/oidc: fix skip_authorization not being synced to oidc_client 2020-03-02 17:40:38 +01:00
a144552059 providers/oidc: fill claims with userinfo 2020-03-01 22:55:56 +01:00
535d529193 ui: fix title, fix navigation on user settings 2020-02-29 14:46:58 +01:00
6ed2e137a2 new release: 0.8.12-beta 2020-02-28 11:54:03 +01:00
45bd63c720 api: update old field names 2020-02-28 11:48:55 +01:00
736e13fc35 ui: add template for csrf errors 2020-02-28 11:41:28 +01:00
966fff008c ui: re-enable branding on navbar 2020-02-28 11:37:07 +01:00
64f15eadbd providers/saml: fix CSRF errors with POST binding 2020-02-28 10:50:16 +01:00
81b66ecdcd core: remove some more dead code, add more help texts for factors 2020-02-27 16:39:30 +01:00
53e5cf7826 admin: fix some models not being paginated 2020-02-27 15:30:28 +01:00
82654b3fd9 ui: re-organize some of the navigation to make it cleaner for end-users 2020-02-27 14:59:34 +01:00
9b72c604dd docs: fix some typos 2020-02-27 13:00:55 +01:00
5fb1b8044c new release: 0.8.11-beta 2020-02-25 11:38:50 +01:00
b8daab4377 providers/saml: fix AccessRequiredView.dispatch not being called 2020-02-25 11:38:26 +01:00
c5b91bdae8 providers/saml: fix CannotHandleAssertion Error still being sent to sentry 2020-02-24 19:14:43 +01:00
39a208c55f providers/saml: fix wrong key being used for params 2020-02-24 17:48:03 +01:00
a5bfef9b6b providers/saml: fix leftover data in session, fix IdP initiated login
move can_handle calls to binding endpoints (/login/ and /login/initiate/), so that /login/authorize/ works either way, can clean up the session and audit
2020-02-24 17:34:52 +01:00
f1f4cbef9b lib/sentry: fix SentryIgnoredException not being ignored correctly 2020-02-24 17:01:31 +01:00
8388120b06 new release: 0.8.10-beta 2020-02-24 15:30:57 +01:00
2bf96828f1 root: fix logging.basicConfig being called by pyjwkest 2020-02-24 15:30:28 +01:00
22838e66fe providers/saml: fix users being able to authenticate without audit logs being created 2020-02-24 14:40:12 +01:00
484dd6de09 providers/oidc: add error template 2020-02-24 14:19:02 +01:00
b743736c26 lib/logging: fix typo 2020-02-24 14:10:58 +01:00
af91e2079b core: sort provider by pk when selection application provider 2020-02-24 14:10:51 +01:00
cad1c17f14 helm: fix inconsistent labels 2020-02-24 13:49:42 +01:00
120d32e4dc new release: 0.8.9-beta 2020-02-24 13:23:20 +01:00
238b489e07 root: add process ID to logging output 2020-02-24 13:20:32 +01:00
4daa70c894 core: fix saving of policy not correctly clearing it's cache 2020-02-24 13:15:52 +01:00
f8599438df ui: fix lists not being rendered correctly 2020-02-24 13:13:42 +01:00
155c9a4c3f ui: update remaining forms, completely remove jQuery 2020-02-24 13:13:28 +01:00
8433b5e583 ui: fix automatic slug generation 2020-02-24 12:40:16 +01:00
dc5ba144f1 ui: fix height of multiple select input 2020-02-24 12:40:06 +01:00
521a8b5356 ui: update more remaining templates 2020-02-23 22:49:56 +01:00
3453077d7b root: set SameSite to None when debugging 2020-02-23 22:49:33 +01:00
70ede8581a core: sort sources on login view 2020-02-23 20:19:01 +01:00
6e9d297f02 deploy: use new bootstrap command 2020-02-23 20:12:48 +01:00
6a7545fd43 lib: add bootstrap command 2020-02-23 19:52:41 +01:00
a8926cbd07 lib: add more errors to sentry ignore 2020-02-23 19:48:14 +01:00
64d7b009ab sources/oauth: fix invalid headers, fix invalid function signature 2020-02-23 19:42:57 +01:00
2b5fddb7bf policies: add unittests for evaluator 2020-02-23 15:54:26 +01:00
b99d23c119 all: remove dead code 2020-02-23 15:32:20 +01:00
03905b74ff admin: exclude anonymous user from listing 2020-02-23 15:27:28 +01:00
6b8a59cfbd admin: show prettified yaml 2020-02-23 15:27:11 +01:00
d6fdcd3ef9 ui: re-add automatic slug generation 2020-02-23 15:20:41 +01:00
53ebc551d2 ui: fix icon sizing on login 2020-02-23 15:13:18 +01:00
3d4f43d6e3 ui: show default icon for source without icon 2020-02-23 15:09:58 +01:00
074cde7cd5 audit: save model's name or string representation 2020-02-23 15:04:30 +01:00
382e563590 new release: 0.8.8-beta 2020-02-23 14:45:45 +01:00
ca61a7cc21 audit: don't pop password as its censored already 2020-02-23 14:43:33 +01:00
fa2870afe0 sources: remove policies as they are not used currently 2020-02-23 14:40:06 +01:00
0f46207ea4 admin: fix provider list not having pagination 2020-02-23 14:29:21 +01:00
1e7d912144 actions: build :<branch-name> on push 2020-02-23 13:54:19 +01:00
f4a676e2fb sources/oauth: slugify provider type instead of just lowercase 2020-02-23 13:53:16 +01:00
b2c10e2387 ui: add missing discord and twitter icons 2020-02-23 13:47:21 +01:00
8c329dca7d core: add migration to fix null fields in core.application 2020-02-22 19:26:34 +01:00
83da175749 policies/expression: add pb_client_ip field 2020-02-22 19:26:16 +01:00
995c87938f core: fix default Null causing issues in translation 2020-02-21 23:10:00 +01:00
40678b2f84 new release: 0.8.7-beta 2020-02-21 22:17:11 +01:00
8dbbe9102b ui: fix application grid icons, fix SAML Authorize 2020-02-21 22:16:58 +01:00
2f51f354de ui: fix app icon not showing 2020-02-21 22:02:44 +01:00
04b815a33e admin: show object usage count instead of list 2020-02-21 22:02:03 +01:00
2a4d68911b helm: change static healthcheck and port 2020-02-21 21:50:16 +01:00
4d5a2d61ff new release: 0.8.6-beta 2020-02-21 21:36:04 +01:00
efd88c27ad audit: *crit* fix audit log not being protected correctly 2020-02-21 21:33:42 +01:00
80d361ccd2 audit: fix failed login events not being logged 2020-02-21 21:27:25 +01:00
6ed4501615 ui: fix branding viewport 2020-02-21 21:18:55 +01:00
8d34faa28e Merge pull request #5 from BeryJu/pf4
PatternFly v4
2020-02-21 21:03:48 +01:00
a3ae827839 ui: centrally load CodeMirror and init via data tag 2020-02-21 21:02:03 +01:00
88c1ad4c1c providers/saml: fix 500 when SAML Provider not assigned to application 2020-02-21 20:54:00 +01:00
1147c4901b ui: clean up some more remaining templates 2020-02-21 20:40:40 +01:00
063181d7a7 ui: use compact templates 2020-02-21 20:24:02 +01:00
1285ba6fbb ui: include font-awesome 2020-02-21 18:00:09 +01:00
a09a1793ec ui: update templates for jinja2-related fields 2020-02-21 15:36:37 +01:00
50caa3ac3e ui: update static docker image to download NPM modules 2020-02-21 15:33:54 +01:00
9440d24358 static: use codemirror from npm 2020-02-21 15:18:13 +01:00
26bf6fd22f all: add more helpful help_text to models 2020-02-21 15:12:16 +01:00
e2f836feae all: fix left over references to error templates 2020-02-21 15:05:32 +01:00
b6326f399c ui: clean up more generic forms, remove is_login everywhere 2020-02-21 15:00:45 +01:00
ea6a1422f7 ui: rewrite admin templates to pf4, add some helper scripts 2020-02-21 14:20:16 +01:00
8fd86a28ff ui: fixup minor issues, add static app 2020-02-21 11:20:55 +01:00
d88283a7a9 Merge branch 'master' into pf4
# Conflicts:
#	passbook/core/static/img/logos/discord.svg
#	passbook/core/static/js/passbook.js
#	passbook/core/templates/login/with_sources.html
#	passbook/core/templates/overview/index.html
#	passbook/core/views/authentication.py
2020-02-21 09:05:40 +01:00
32a15f84c0 root: run bandit as part of pre-commit 2020-02-21 09:03:59 +01:00
93ba4b7f62 root: use defusedxml's defuse_stdlib globally 2020-02-21 09:00:28 +01:00
187780dab2 new release: 0.8.5-beta 2020-02-20 21:39:13 +01:00
d988f37afc lib: add SentryIgnoredException, to easily ignore exceptions from sentry 2020-02-20 21:38:53 +01:00
295c0bae3f sources/saml: validate SAMLResponse signature 2020-02-20 21:34:25 +01:00
38a22ddf13 providers/saml: cleanup encoding 2020-02-20 21:33:10 +01:00
d06f1abb89 providers/saml: add POST binding support to Metadata 2020-02-20 17:38:42 +01:00
027a64fad2 providers/saml: change default NameID Format to emailAddress 2020-02-20 17:37:09 +01:00
84fc54ddaa sources/saml: entity_id -> issuer 2020-02-20 17:23:27 +01:00
0b5caa85f5 all: sort imports and cleanup 2020-02-20 17:23:05 +01:00
14e0a17dbc ui: don't remove dashes when auto generating slug 2020-02-20 17:13:50 +01:00
3c04afa31f root: use different cookie names for dev instance 2020-02-20 17:08:11 +01:00
40a2a26904 sources/saml: fix Metadata cert including PEM header 2020-02-20 17:05:11 +01:00
c8b3c6e51a sources/saml: fix build_full_url using incorrect URL parameter 2020-02-20 17:04:54 +01:00
e0272a6422 providers/saml: Show error message when trying to get metadata without assigning application 2020-02-20 17:04:20 +01:00
b290bbf6d7 new release: 0.8.4-beta 2020-02-20 16:17:23 +01:00
8d875cb01d providers/saml: fix /login/ pointing to wrong view 2020-02-20 16:13:55 +01:00
36b1f8ba36 new release: 0.8.3-beta 2020-02-20 15:14:49 +01:00
6c889eff27 core: fix application icons not loading, fix with_sources being broken 2020-02-20 14:30:06 +01:00
9d8675e54b new release: 0.8.2-beta 2020-02-20 13:57:46 +01:00
22ae986c0b root: add logger name to log output 2020-02-20 13:52:14 +01:00
2bef5f3911 policies: struct -> types to match core 2020-02-20 13:52:05 +01:00
3c2b8e5ee1 all: prefix all UI related methods with ui_, switch to property and return dataclass 2020-02-20 13:51:41 +01:00
c96571bdba core: fix discord logo being hard to see 2020-02-20 13:50:05 +01:00
2dfd93afb1 core: add more fields for metadata of applications 2020-02-20 13:45:22 +01:00
f1d77d475c ui: start patternfly v4 migration 2020-02-19 21:03:39 +01:00
1d22e30c70 lib: sentry ignore Redis and OSError 2020-02-19 17:13:44 +01:00
07b7951390 sources/ldap: handle user_sync errors better, show warning when user exists already 2020-02-19 16:20:33 +01:00
995615d0a0 policies/expression: Return False if Policy returns Undefined and log warning 2020-02-19 16:19:02 +01:00
ac273aab75 core: raise PropertyMappingExpressionException when PropertyMapping returns Undefined 2020-02-19 16:18:31 +01:00
44cd03654d core: base set maximum-scale to 1 2020-02-19 15:11:25 +01:00
3e2375f970 new release: 0.8.1-beta 2020-02-19 11:31:05 +01:00
38ad8e5fd3 policies/expression: fix pb_is_sso_flow 2020-02-19 11:01:20 +01:00
c481558a46 helm: fix error that FLUSHDB Command is not available 2020-02-19 10:57:57 +01:00
e27a05a7fc lib/sentry: ignore django validation error 2020-02-19 10:54:29 +01:00
1175 changed files with 21657 additions and 278582 deletions

View File

@ -1,5 +1,5 @@
[bumpversion]
current_version = 0.8.0-beta
current_version = 0.9.0-pre1
tag = True
commit = True
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*)

View File

@ -3,3 +3,4 @@ helm
passbook-ui
static
*.env.yml
node_modules/

20
.fossa.yml Executable file
View File

@ -0,0 +1,20 @@
# Generated by FOSSA CLI (https://github.com/fossas/fossa-cli)
# Visit https://fossa.com to learn more
version: 2
cli:
server: https://app.fossa.com
fetcher: custom
project: git@github.com:BeryJu/passbook.git
analyze:
modules:
- name: static
type: npm
target: passbook/static/static
path: passbook/static/static
- name: .
type: pip
target: .
path: .
options:
strategy: pipenv

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
custom: ["https://www.paypal.me/octocat"]

38
.github/workflows/ci-cleanup.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: passbook-ci-cleanup
on:
- delete
jobs:
delete-server:
runs-on: ubuntu-latest
steps:
- name: Delete docker tag
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: curl
-u $DOCKER_USERNAME:$DOCKER_PASSWORD
-X "DELETE"
"https://hub.docker.com/v2/repositories/$DOCKER_USERNAME/passbook/tags/${GITHUB_REF##*/}/"
delete-gatekeeper:
runs-on: ubuntu-latest
steps:
- name: Delete docker tag
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: curl
-u $DOCKER_USERNAME:$DOCKER_PASSWORD
-X "DELETE"
"https://hub.docker.com/v2/repositories/$DOCKER_USERNAME/passbook-gatekeeper/tags/${GITHUB_REF##*/}/"
delete-static:
runs-on: ubuntu-latest
steps:
- name: Delete docker tag
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: curl
-u $DOCKER_USERNAME:$DOCKER_PASSWORD
-X "DELETE"
"https://hub.docker.com/v2/repositories/$DOCKER_USERNAME/passbook-static/tags/${GITHUB_REF##*/}/"

View File

@ -15,14 +15,8 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
restore-keys: |
${{ runner.os }}-pipenv-
- name: Install dependencies
run: pip install -U pip pipenv && pipenv install --dev
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Lint with pylint
run: pipenv run pylint passbook
black:
@ -32,14 +26,8 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
restore-keys: |
${{ runner.os }}-pipenv-
- name: Install dependencies
run: pip install -U pip pipenv && pipenv install --dev
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Lint with black
run: pipenv run black --check passbook
prospector:
@ -49,14 +37,8 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
restore-keys: |
${{ runner.os }}-pipenv-
- name: Install dependencies
run: pip install -U pip pipenv && pipenv install --dev && pipenv install --dev prospector --skip-lock
run: sudo pip install -U wheel pipenv && pipenv install --dev && pipenv install --dev prospector --skip-lock
- name: Lint with prospector
run: pipenv run prospector
bandit:
@ -66,16 +48,26 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
restore-keys: |
${{ runner.os }}-pipenv-
- name: Install dependencies
run: pip install -U pip pipenv && pipenv install --dev
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Lint with bandit
run: pipenv run bandit -r passbook
pyright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/setup-node@v1
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- name: Install pyright
run: npm install -g pyright
- name: Show pyright version
run: pyright --version
- name: Install dependencies
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Lint with pyright
run: pipenv run pyright
# Actual CI tests
migrations:
needs:
@ -101,14 +93,8 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
restore-keys: |
${{ runner.os }}-pipenv-
- name: Install dependencies
run: pip install -U pip pipenv && pipenv install --dev
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Run migrations
run: pipenv run ./manage.py migrate
coverage:
@ -135,13 +121,82 @@ jobs:
- uses: actions/setup-python@v1
with:
python-version: '3.8'
- uses: actions/cache@v1
with:
path: ~/.local/share/virtualenvs/
key: ${{ runner.os }}-pipenv-${{ hashFiles('Pipfile.lock') }}
restore-keys: |
${{ runner.os }}-pipenv-
- name: Install dependencies
run: pip install -U pip pipenv && pipenv install --dev
run: sudo pip install -U wheel pipenv && pipenv install --dev
- name: Run coverage
run: pipenv run ./scripts/coverage.sh
- name: Create XML Report
run: pipenv run coverage xml
- uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
# Build
build-server:
needs:
- migrations
- coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Docker Login Registry
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/passbook:${GITHUB_REF##*/}
-f Dockerfile .
- name: Push Docker Container to Registry
run: docker push beryju/passbook:${GITHUB_REF##*/}
build-gatekeeper:
needs:
- migrations
- coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Docker Login Registry
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- name: Building Docker Image
run: |
cd gatekeeper
docker build \
--no-cache \
-t beryju/passbook-gatekeeper:${GITHUB_REF##*/} \
-f Dockerfile .
- name: Push Docker Container to Registry
run: docker push beryju/passbook-gatekeeper:${GITHUB_REF##*/}
build-static:
needs:
- migrations
- coverage
runs-on: ubuntu-latest
services:
postgres:
image: postgres:latest
env:
POSTGRES_DB: passbook
POSTGRES_USER: passbook
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
redis:
image: redis:latest
steps:
- uses: actions/checkout@v1
- name: Docker Login Registry
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD
- name: Building Docker Image
run: docker build
--no-cache
--network=$(docker network ls | grep github | awk '{print $1}')
-t beryju/passbook-static:${GITHUB_REF##*/}
-f static.Dockerfile .
- name: Push Docker Container to Registry
run: docker push beryju/passbook-static:${GITHUB_REF##*/}

View File

@ -16,11 +16,11 @@ jobs:
- name: Building Docker Image
run: docker build
--no-cache
-t beryju/passbook:0.8.0-beta
-t beryju/passbook:0.9.0-pre1
-t beryju/passbook:latest
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook:0.8.0-beta
run: docker push beryju/passbook:0.9.0-pre1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook:latest
build-gatekeeper:
@ -37,11 +37,11 @@ jobs:
cd gatekeeper
docker build \
--no-cache \
-t beryju/passbook-gatekeeper:0.8.0-beta \
-t beryju/passbook-gatekeeper:0.9.0-pre1 \
-t beryju/passbook-gatekeeper:latest \
-f Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-gatekeeper:0.8.0-beta
run: docker push beryju/passbook-gatekeeper:0.9.0-pre1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-gatekeeper:latest
build-static:
@ -66,11 +66,11 @@ jobs:
run: docker build
--no-cache
--network=$(docker network ls | grep github | awk '{print $1}')
-t beryju/passbook-static:0.8.0-beta
-t beryju/passbook-static:0.9.0-pre1
-t beryju/passbook-static:latest
-f static.Dockerfile .
- name: Push Docker Container to Registry (versioned)
run: docker push beryju/passbook-static:0.8.0-beta
run: docker push beryju/passbook-static:0.9.0-pre1
- name: Push Docker Container to Registry (latest)
run: docker push beryju/passbook-static:latest
test-release:

6
.isort.cfg Normal file
View File

@ -0,0 +1,6 @@
[settings]
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
use_parentheses=True
line_length=88

View File

@ -8,4 +8,4 @@ ignore-paths:
- node_modules
uses:
- django
- django

View File

@ -1,13 +1,9 @@
[MASTER]
disable=redefined-outer-name,arguments-differ,no-self-use,cyclic-import,fixme,locally-disabled,unpacking-non-sequence,too-many-ancestors,too-many-branches,too-few-public-methods,import-outside-toplevel,bad-continuation
disable=redefined-outer-name,arguments-differ,no-self-use,cyclic-import,fixme,locally-disabled,too-many-ancestors,too-few-public-methods,import-outside-toplevel,bad-continuation,signature-differs
load-plugins=pylint_django,pylint.extensions.bad_builtin
extension-pkg-whitelist=lxml
const-rgx=[a-zA-Z0-9_]{1,40}$
ignored-modules=django-otp
jobs=4
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=20
jobs=12
ignore=migrations

View File

@ -25,7 +25,11 @@ RUN apt-get update && \
COPY ./passbook/ /app/passbook
COPY ./manage.py /app/
COPY ./docker/uwsgi.ini /app/
COPY ./docker/bootstrap.sh /bootstrap.sh
COPY ./docker/wait_for_db.py /app/wait_for_db.py
WORKDIR /app/
USER passbook
ENTRYPOINT [ "/bootstrap.sh" ]

View File

@ -40,7 +40,6 @@ signxml = "*"
structlog = "*"
swagger-spec-validator = "*"
urllib3 = {extras = ["secure"],version = "*"}
jinja2 = "*"
[requires]
python_version = "3.8"

836
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,92 @@
# passbook
<img src="passbook/static/static/passbook/logo.svg" height="50" alt="passbook logo"><img src="passbook/static/static/passbook/brand_inverted.svg" height="50" alt="passbook">
![](https://github.com/BeryJu/passbook/workflows/passbook-ci/badge.svg)
![](https://img.shields.io/github/workflow/status/beryju/passbook/passbook-ci?style=flat-square)
![](https://img.shields.io/docker/pulls/beryju/passbook.svg?style=flat-square)
![](https://img.shields.io/docker/pulls/beryju/passbook-gatekeeper.svg?style=flat-square)
![](https://img.shields.io/docker/pulls/beryju/passbook-static.svg?style=flat-square)
![](https://img.shields.io/docker/v/beryju/passbook?sort=semver&style=flat-square)
![](https://img.shields.io/codecov/c/gh/beryju/passbook?style=flat-square)
## Quick instance
## What is passbook?
passbook is an open-source Identity Provider focused on flexibility and versatility. You can use passbook in an existing environment to add support for new protocols. passbook is also a great solution for implementing signup/recovery/etc in your application, so you don't have to deal with it.
## Installation
For small/test setups it is recommended to use docker-compose.
```
export PASSBOOK_DOMAIN=domain.tld
wget https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml
# Optionally enable Error-reporting
# export PASSBOOK_ERROR_REPORTING=true
# Optionally deploy a different version
# export PASSBOOK_TAG=0.8.15-beta
# If this is a productive installation, set a different PostgreSQL Password
# export PG_PASS=$(pwgen 40 1)
docker-compose pull
docker-compose up -d
docker-compose exec server ./manage.py migrate
docker-compose exec server ./manage.py createsuperuser
```
For bigger setups, there is a Helm Chart in the `helm/` directory. This is documented [here](https://passbook.beryju.org//installation/kubernetes/)
## Screenshots
![](docs/images/screen_apps.png)
![](docs/images/screen_admin.png)
## Development
To develop on passbook, you need a system with Python 3.7+ (3.8 is recommended). passbook uses [pipenv](https://pipenv.pypa.io/en/latest/) for managing dependencies.
To get started, run
```
python3 -m pip install pipenv
git clone https://github.com/BeryJu/passbook.git
cd passbook
pipenv shell
pipenv sync -d
```
Since passbook uses PostgreSQL-specific fields, you also need a local PostgreSQL instance to develop. passbook also uses redis for caching and message queueing.
For these databases you can use [Postgres.app](https://postgresapp.com/) and [Redis.app](https://jpadilla.github.io/redisapp/) on macOS or use it via docker-comppose:
```yaml
version: '3.7'
services:
postgresql:
container_name: postgres
image: postgres:11
volumes:
- db-data:/var/lib/postgresql/data
ports:
- 127.0.0.1:5432:5432
restart: always
redis:
container_name: redis
image: redis
ports:
- 127.0.0.1:6379:6379
restart: always
volumes:
db-data:
driver: local
```
To tell passbook about these databases, create a file in the project root called `local.env.yml` with the following contents:
```yaml
debug: true
postgresql:
user: postgres
log_level: debug
error_reporting: false
```
## Security
See [SECURITY.md](SECURITY.md)

13
SECURITY.md Normal file
View File

@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
As passbook is currently in a pre-stable, only the latest "stable" version is supported. After passbook 1.0, this will change.
| Version | Supported |
| -------- | ------------------ |
| 0.8.15 | :white_check_mark: |
## Reporting a Vulnerability
To report a vulnerability, send am email to [security@beryju.org](mailto:security@beryju.org)

View File

@ -21,12 +21,11 @@ services:
labels:
- traefik.enable=false
server:
image: beryju/passbook:${SERVER_TAG:-latest}
image: beryju/passbook:${PASSBOOK_TAG:-latest}
command:
- uwsgi
- uwsgi.ini
environment:
- PASSBOOK_DOMAIN=${PASSBOOK_DOMAIN}
- PASSBOOK_REDIS__HOST=redis
- PASSBOOK_ERROR_REPORTING=${PASSBOOK_ERROR_REPORTING:-false}
- PASSBOOK_POSTGRESQL__HOST=postgresql
@ -40,7 +39,7 @@ services:
- traefik.docker.network=internal
- traefik.frontend.rule=PathPrefix:/
worker:
image: beryju/passbook:${SERVER_TAG:-latest}
image: beryju/passbook:${PASSBOOK_TAG:-latest}
command:
- celery
- worker
@ -54,7 +53,6 @@ services:
labels:
- traefik.enable=false
environment:
- PASSBOOK_DOMAIN=${PASSBOOK_DOMAIN}
- PASSBOOK_REDIS__HOST=redis
- PASSBOOK_ERROR_REPORTING=${PASSBOOK_ERROR_REPORTING:-false}
- PASSBOOK_POSTGRESQL__HOST=postgresql
@ -65,7 +63,7 @@ services:
- internal
labels:
- traefik.frontend.rule=PathPrefix:/static, /robots.txt
- traefik.port=8080
- traefik.port=80
- traefik.docker.network=internal
traefik:
image: traefik:1.7

3
docker/bootstrap.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash -ex
/app/wait_for_db.py
"$@"

41
docker/wait_for_db.py Executable file
View File

@ -0,0 +1,41 @@
#!/usr/bin/env python
"""This file needs to be run from the root of the project to correctly
import passbook. This is done by the dockerfile."""
from time import sleep
from psycopg2 import OperationalError, connect
from redis import Redis
from redis.exceptions import RedisError
from structlog import get_logger
from passbook.lib.config import CONFIG
LOGGER = get_logger()
while True:
try:
conn = connect(
dbname=CONFIG.y("postgresql.name"),
user=CONFIG.y("postgresql.user"),
password=CONFIG.y("postgresql.password"),
host=CONFIG.y("postgresql.host"),
)
conn.cursor()
break
except OperationalError:
sleep(1)
LOGGER.warning("PostgreSQL Connection failed, retrying...")
while True:
try:
redis = Redis(
host=CONFIG.y("redis.host"),
port=6379,
db=CONFIG.y("redis.message_queue_db"),
password=CONFIG.y("redis.password"),
)
redis.ping()
break
except RedisError:
sleep(1)
LOGGER.warning("Redis Connection failed, retrying...")

View File

@ -1,14 +0,0 @@
FROM python:3.8-slim-buster as builder
WORKDIR /mkdocs
RUN pip install mkdocs mkdocs-material
COPY docs/ docs
COPY mkdocs.yml .
RUN mkdocs build
FROM nginx
COPY --from=builder /mkdocs/site /usr/share/nginx/html

3
docs/build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash -x
pip install -U mkdocs mkdocs-material
mkdocs gh-deploy

55
docs/expressions/index.md Normal file
View File

@ -0,0 +1,55 @@
# Expressions
Expressions allow you to write custom Logic using Python code.
Expressions are used in different places throughout passbook, and can do different things.
!!! info
These functions/objects are available wherever expressions are used. For more specific information, see [Expression Policies](../policies/expression.md) and [Property Mappings](../property-mappings/expression.md)
## Global objects
- `pb_logger`: structlog BoundLogger. ([ref](https://www.structlog.org/en/stable/api.html#structlog.BoundLogger))
- `requests`: requests Session object. ([ref](https://requests.readthedocs.io/en/master/user/advanced/))
## Generally available functions
### `regex_match(value: Any, regex: str) -> bool`
Check if `value` matches Regular Expression `regex`.
Example:
```python
return regex_match(request.user.username, '.*admin.*')
```
### `regex_replace(value: Any, regex: str, repl: str) -> str`
Replace anything matching `regex` within `value` with `repl` and return it.
Example:
```python
user_email_local = regex_replace(request.user.email, '(.+)@.+', '')
```
### `pb_is_group_member(user: User, **group_filters) -> bool`
Check if `user` is member of a group matching `**group_filters`.
Example:
```python
return pb_is_group_member(request.user, name="test_group")
```
### `pb_user_by(**filters) -> Optional[User]`
Fetch a user matching `**filters`. Returns None if no user was found.
Example:
```python
other_user = pb_user_by(username="other_user")
```

View File

@ -15,6 +15,7 @@ The User object has the following attributes:
List all the User's Group Names
```jinja2
[{% for group in user.groups.all() %}'{{ group.name }}',{% endfor %}]
```python
for group in user.groups.all():
yield group.name
```

View File

@ -1,23 +0,0 @@
# Factors
A factor represents a single authenticating factor for a user. Common examples of this would be a password or an OTP. These factors can be combined in any order, and can be dynamically enabled using policies.
## Password Factor
This is the standard Password Factor. It allows you to select which Backend the password is checked with. here you can also specify which Policies are used to check the password. You can also specify which Factors a User has to pass to recover their account.
## Dummy Factor
This factor waits a random amount of time. Mostly used for debugging.
## E-Mail Factor
This factor is mostly for recovery, and used in conjunction with the Password Factor.
## OTP Factor
This is your typical One-Time Password implementation, compatible with Authy and Google Authenticator. You can enfore this Factor so that every user has to configure it, or leave it optional.
## Captcha Factor
While this factor doesn't really authenticate a user, it is part of the Authentication Flow. passbook uses Google's reCaptcha implementation.

View File

@ -0,0 +1,36 @@
# Login Flow
This document describes how a simple authentication flow can be created.
This flow is created automatically when passbook is installed.
1. Create an **Identification** stage
> Here you can select whichever fields the user can identify themselves with
> Select the Template **Default Login**, as this template shows the (optional) Flows
> Here you can also link optional enrollment and recovery flows.
2. Create a **Password** stage
> Select the Backend you want the password to be checked against. Select "passbook-internal Userdatabase".
3. Create a **User Login** stage
> This stage doesn't have any options.
4. Create a flow
> Create a flow with the delegation of **Authentication**
> Assign a name and a slug. The slug is used in the URL when the flow is executed.
5. Bind the stages to the flow
> Bind the **Identification** Stage with an order of 0
> Bind the **Password** Stage with an order of 1
> Bind the **User Login** Stage with an order of 2
![](login.png)
!!! notice
This flow can used by any user, authenticated and un-authenticated. This means any authenticated user that visits this flow can login again.

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

45
docs/flow/flows.md Normal file
View File

@ -0,0 +1,45 @@
# Flows
Flows are a method of describing a sequence of stages. A stage represents a single verification or logic step. They are used to authenticate users, enroll them, and more.
Upon Flow execution, a plan is generated, which contains all stages. This means upon execution, all attached policies are evaluated. This behaviour can be altered by enabling the **Re-evaluate Policies** option on the binding.
To determine which flow is linked, passbook searches all Flows with the required designation and chooses the first instance the current user has access to.
## Permissions
Flows can have policies assigned to them, which determines if the current user is allowed to see and use this flow.
## Designation
Flows are designated for a single Purpose. This designation changes when a Flow is used. The following designations are available:
### Authentication
This is designates a flow to be used for authentication.
The authentication flow should always contain a [**User Login**](stages/user_login.md) stage, which attaches the staged user to the current session.
### Invalidation
This designates a flow to be used for the invalidation of a session.
This stage should always contain a [**User Logout**](stages/user_logout.md) stage, which resets the current session.
### Enrollment
This designates a flow for enrollment. This flow can contain any amount of Prompt stages, E-Mail verification or Captchas. At the end to create the user, you can use the [**User Write**](stages/user_write.md) stage, which either updates the currently staged user, or if none exists, creates a new one.
### Unenrollment
This designates a flow for unenrollment. This flow can contain any amount of verification, like [**E-Mail**](stages/email/index.md) or [**Captcha**](stages/captcha/index.md). To finally delete the account, use the [**User Delete**](stages/user_delete.md) stage.
### Recovery
This designates a flow for recovery. This flow normally contains an [**Identification**](stages/identification/index.md) stage to find the user. Then it can contain any amount of verification, like [**E-Mail**](stages/email/index.md) or [**Captcha**](stages/captcha/index.md).
Afterwards, use the [**Prompt**](stages/prompt/index.md) stage to ask the user for a new password and use [**User Write**](stages/user_write.md) to update the password.
### Change Password
This designates a flow for password changing. This flow can contain any amount of verification, like [**E-Mail**](stages/email/index.md) or [**Captcha**](stages/captcha/index.md).
Afterwards, use the [**Prompt**](stages/prompt/index.md) stage to ask the user for a new password and use [**User Write**](stages/user_write.md) to update the password.

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@ -0,0 +1,7 @@
# Captcha stage
This stage adds a form of verification using [Google's ReCaptcha](https://www.google.com/recaptcha/intro/v3.html).
This stage has two required fields. You need a Public and a Private key, both of which you can acquire at https://www.google.com/recaptcha/admin.
![](captcha-admin.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -0,0 +1,5 @@
# Dummy stage
This stage is used for development, and has no function. It presents the User with a form, that requires a single confirmation.
![](dummy.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -0,0 +1,5 @@
# E-Mail
This stage can be used for E-Mail verification. passbook's background worker will send an E-Mail using the specified connection details. When an E-Mail can't be delivered, it is automatically periodically retried.
![](email-recovery.png)

View File

@ -0,0 +1,25 @@
# Identification
This stage provides a ready-to-go form for users to identify themselves.
## Options
### User Fields
Select which fields the user can use to identify themselves. Multiple fields can be specified and separated with a comma.
Valid choices:
- email
- username
### Template
This specifies which template is rendered. Currently there are two templates.
The `Login` template shows configured Sources below the login form, as well as linking to the defined Enrollment and Recovery flows.
The `Recovery` template shows only the form.
### Enrollment/Recovery Flow
These fields specify if and which flows are linked on the form. The enrollment flow is linked as `Need an account? Sign up.`, and the recovery flow is linked as `Forgot username or password?`.

View File

@ -0,0 +1,7 @@
# Invitation Stage
This stage can be used to invite users. You can use this enroll users with preset values.
If the option `Continue Flow without Invitation`, this stage will continue when no invitation token is present.
If you want to check if a user has used an invitation within a policy, you can check `request.context.invitation_in_effect`.

View File

@ -0,0 +1,7 @@
# OTP Stage
This stage offers a generic Time-based One-time Password authentication step.
You can optionally enforce this step, which will force every user without OTP setup to configure it.
This stage uses a 6-digit Code with a 30 second time-drift. This is currently not changeable.

View File

@ -0,0 +1,3 @@
# Password Stage
This is a generic password prompt, which authenticates the currently `pending_user`. This stage allows the selection of the Backend the user is authenticated against.

View File

@ -0,0 +1,42 @@
# Prompt Stage
This stage is used to show the user arbitrary prompts.
## Prompt
The prompt can be any of the following types:
| | |
|----------|------------------------------------------------------------------|
| text | Arbitrary text, no client-side validation is done. |
| email | E-Mail input, requires a valid E-Mail adress |
| password | Password Input |
| number | Number Input, any number is allowed |
| checkbox | Simple Checkbox |
| hidden | Hidden Input field, allows for the pre-setting of default values |
A Prompt has the following attributes:
### `field_key`
HTML name used for the prompt. This key is also used to later retrieve the data in expression policies:
```python
request.context.get('prompt_data').get('<field_key>')
```
### `label`
Label used to describe the Field. This might not be shown depending on the template selected.
### `required`
Flag that decides whether or not this field is required.
### `placeholder`
Field placeholder, shown within the input field. This field is also used by the `hidden` type as the actual value.
### `order`
Numerical index of the prompt. This applies to all stages this prompt is a part of.

View File

@ -0,0 +1,16 @@
# Prompt Validation
Further validation of prompts can be done using policies.
To validate that two password fields are identical, create the following expression policy:
```python
if request.context.get('prompt_data').get('password') == request.context.get('prompt_data').get('password_repeat'):
return True
pb_message("Passwords don't match.")
return False
```
This policy expects you two have two password fields with `field_key` set to `password` and `password_repeat`.
Afterwards bind this policy to the prompt stage you want to validate.

View File

@ -0,0 +1,8 @@
# User Delete Stage
!!! danger
This stage deletes the `pending_user` without any confirmation. You have to make sure the user is aware of this.
This stage is intended for an unenrollment flow. It deletes the currently pending user.
The pending user is also removed from the current session.

View File

@ -0,0 +1,5 @@
# User Login Stage
This stage attaches a currently pending user to the current session.
It can be used after `user_write` during an enrollment flow, or after a `password` stage during an authentication flow.

View File

@ -0,0 +1,3 @@
# User Logout Stage
Opposite stage of [User Login Stages](user_login.md). It removes the user from the current session.

View File

@ -0,0 +1,3 @@
# User Write Stage
This stages writes data from the current context to the current pending user. If no user is pending, a new one is created.

View File

@ -1,2 +1,2 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="270px" height="20px" viewBox="0 0 270 10" enable-background="new 0 0 270 10" xml:space="preserve"><defs><style>.cls-1{isolation:isolate;}.cls-2{fill:#fff;}</style></defs><g class="cls-1"><path class="cls-2" d="M1.65,11V2.45H2.87V3a2.81,2.81,0,0,1,.47-.45A1.13,1.13,0,0,1,4,2.38,1.11,1.11,0,0,1,5.1,3a1.55,1.55,0,0,1,.16.5,5.61,5.61,0,0,1,0,.81V6.58c0,.45,0,.77,0,1a1.17,1.17,0,0,1-.55.9,1.23,1.23,0,0,1-.7.16,1.35,1.35,0,0,1-.64-.16A1.53,1.53,0,0,1,2.89,8h0v3ZM4.08,4.43a1.21,1.21,0,0,0-.14-.6.51.51,0,0,0-.46-.22A.54.54,0,0,0,3,3.82a.8.8,0,0,0-.17.54V6.73A.68.68,0,0,0,3,7.2a.6.6,0,0,0,.44.18A.53.53,0,0,0,4,7.17a1,1,0,0,0,.12-.5Z"/><path class="cls-2" d="M8.63,8.54V7.91h0a2.24,2.24,0,0,1-.48.52,1.13,1.13,0,0,1-.69.18A1.39,1.39,0,0,1,7,8.54a1.09,1.09,0,0,1-.43-.24,1.32,1.32,0,0,1-.33-.49A2.33,2.33,0,0,1,6.11,7a4.89,4.89,0,0,1,.08-.91,1.51,1.51,0,0,1,.31-.65,1.44,1.44,0,0,1,.59-.38A3.19,3.19,0,0,1,8,4.93h.59V4.33a1,1,0,0,0-.13-.52A.52.52,0,0,0,8,3.61a.71.71,0,0,0-.44.15.78.78,0,0,0-.26.46H6.13A2,2,0,0,1,6.69,2.9a1.73,1.73,0,0,1,.57-.38A2,2,0,0,1,8,2.38a2.18,2.18,0,0,1,.72.12,1.71,1.71,0,0,1,.59.36,2,2,0,0,1,.38.6,2.18,2.18,0,0,1,.14.84V8.54Zm0-2.62-.34,0a1.2,1.2,0,0,0-.67.18.76.76,0,0,0-.29.68.89.89,0,0,0,.17.56A.55.55,0,0,0,8,7.53a.63.63,0,0,0,.49-.2.91.91,0,0,0,.17-.58Z"/><path class="cls-2" d="M13,4.16a.59.59,0,0,0-.2-.47.65.65,0,0,0-.42-.16.59.59,0,0,0-.45.19.66.66,0,0,0-.15.43.8.8,0,0,0,.08.33.85.85,0,0,0,.44.29l.71.29a1.73,1.73,0,0,1,.95.72,2,2,0,0,1,.26,1,1.85,1.85,0,0,1-.52,1.3,1.56,1.56,0,0,1-.58.39,1.88,1.88,0,0,1-2-.32,1.58,1.58,0,0,1-.4-.57,1.81,1.81,0,0,1-.17-.8h1.15a1.11,1.11,0,0,0,.17.47.56.56,0,0,0,.49.22.71.71,0,0,0,.47-.18A.59.59,0,0,0,13,6.8a.69.69,0,0,0-.13-.43,1.08,1.08,0,0,0-.48-.32l-.59-.21a2.08,2.08,0,0,1-.9-.64,1.66,1.66,0,0,1-.33-1,1.89,1.89,0,0,1,.14-.72,1.78,1.78,0,0,1,.4-.57,1.5,1.5,0,0,1,.56-.36,1.82,1.82,0,0,1,.7-.13,1.93,1.93,0,0,1,.69.13,1.6,1.6,0,0,1,.54.38,1.85,1.85,0,0,1,.36.57,1.82,1.82,0,0,1,.13.7Z"/><path class="cls-2" d="M17.2,4.16a.63.63,0,0,0-.2-.47.69.69,0,0,0-.43-.16.55.55,0,0,0-.44.19.62.62,0,0,0-.16.43.68.68,0,0,0,.09.33.81.81,0,0,0,.43.29l.72.29a1.7,1.7,0,0,1,.94.72,2,2,0,0,1,.26,1,1.85,1.85,0,0,1-.52,1.3,1.61,1.61,0,0,1-.57.39,1.81,1.81,0,0,1-.74.15,1.76,1.76,0,0,1-1.24-.47,1.61,1.61,0,0,1-.41-.57,2,2,0,0,1-.17-.8h1.15a1.12,1.12,0,0,0,.18.47.53.53,0,0,0,.48.22.72.72,0,0,0,.48-.18.59.59,0,0,0,.21-.48.69.69,0,0,0-.14-.43,1,1,0,0,0-.48-.32l-.58-.21a2.06,2.06,0,0,1-.91-.64,1.66,1.66,0,0,1-.33-1A1.89,1.89,0,0,1,15,3.44a1.78,1.78,0,0,1,.4-.57,1.58,1.58,0,0,1,.56-.36,1.82,1.82,0,0,1,.7-.13,1.93,1.93,0,0,1,.69.13,1.75,1.75,0,0,1,.55.38,1.85,1.85,0,0,1,.36.57,2,2,0,0,1,.13.7Z"/><path class="cls-2" d="M19.2,8.54V0h1.22V3h0a1.53,1.53,0,0,1,.48-.47,1.39,1.39,0,0,1,.65-.16,1.26,1.26,0,0,1,.69.16,1.35,1.35,0,0,1,.4.39,1.18,1.18,0,0,1,.15.51,7.72,7.72,0,0,1,0,1V6.73a5.56,5.56,0,0,1-.05.8,1.56,1.56,0,0,1-.15.5,1.12,1.12,0,0,1-1.07.58,1.15,1.15,0,0,1-.7-.18A3.79,3.79,0,0,1,20.42,8v.55Zm2.44-4.21a1,1,0,0,0-.13-.51A.5.5,0,0,0,21,3.61a.57.57,0,0,0-.44.18.66.66,0,0,0-.18.48V6.63a.83.83,0,0,0,.17.54.52.52,0,0,0,.45.21.49.49,0,0,0,.45-.22,1.11,1.11,0,0,0,.15-.6Z"/><path class="cls-2" d="M23.76,4.49a4.83,4.83,0,0,1,0-.68A1.55,1.55,0,0,1,24,3.26a1.59,1.59,0,0,1,.62-.64,1.84,1.84,0,0,1,1-.24,1.87,1.87,0,0,1,1,.24,1.59,1.59,0,0,1,.62.64,1.55,1.55,0,0,1,.18.55,4.83,4.83,0,0,1,.05.68v2a4.72,4.72,0,0,1-.05.68,1.55,1.55,0,0,1-.18.55,1.59,1.59,0,0,1-.62.64,1.87,1.87,0,0,1-1,.24,1.84,1.84,0,0,1-1-.24A1.59,1.59,0,0,1,24,7.73a1.55,1.55,0,0,1-.18-.55,4.72,4.72,0,0,1,0-.68ZM25,6.69a.72.72,0,0,0,.17.52.53.53,0,0,0,.43.17A.55.55,0,0,0,26,7.21a.72.72,0,0,0,.16-.52V4.3A.74.74,0,0,0,26,3.78a.55.55,0,0,0-.44-.17.53.53,0,0,0-.43.17A.74.74,0,0,0,25,4.3Z"/><path class="cls-2" d="M28.2,4.49a4.83,4.83,0,0,1,.05-.68,1.55,1.55,0,0,1,.18-.55,1.59,1.59,0,0,1,.62-.64,1.84,1.84,0,0,1,1-.24,1.87,1.87,0,0,1,1,.24,1.59,1.59,0,0,1,.62.64,1.55,1.55,0,0,1,.18.55,4.83,4.83,0,0,1,.05.68v2a4.72,4.72,0,0,1-.05.68,1.55,1.55,0,0,1-.18.55,1.59,1.59,0,0,1-.62.64,1.87,1.87,0,0,1-1,.24,1.84,1.84,0,0,1-1-.24,1.59,1.59,0,0,1-.62-.64,1.55,1.55,0,0,1-.18-.55,4.72,4.72,0,0,1-.05-.68Zm1.22,2.2a.72.72,0,0,0,.17.52.53.53,0,0,0,.43.17.55.55,0,0,0,.44-.17.72.72,0,0,0,.16-.52V4.3a.74.74,0,0,0-.16-.52A.55.55,0,0,0,30,3.61a.53.53,0,0,0-.43.17.74.74,0,0,0-.17.52Z"/><path class="cls-2" d="M32.75,8.54V0H34V5.11h0l1.47-2.66H36.7L35.24,4.93,37,8.54H35.66l-1.1-2.63L34,6.83V8.54Z"/></g></svg>
width="120px" height="20px" viewBox="15 0 10 10" enable-background="new 0 0 270 10" xml:space="preserve"><defs><style>.cls-1{isolation:isolate;}.cls-2{fill:#fff;}</style></defs><g class="cls-1"><path class="cls-2" d="M1.65,11V2.45H2.87V3a2.81,2.81,0,0,1,.47-.45A1.13,1.13,0,0,1,4,2.38,1.11,1.11,0,0,1,5.1,3a1.55,1.55,0,0,1,.16.5,5.61,5.61,0,0,1,0,.81V6.58c0,.45,0,.77,0,1a1.17,1.17,0,0,1-.55.9,1.23,1.23,0,0,1-.7.16,1.35,1.35,0,0,1-.64-.16A1.53,1.53,0,0,1,2.89,8h0v3ZM4.08,4.43a1.21,1.21,0,0,0-.14-.6.51.51,0,0,0-.46-.22A.54.54,0,0,0,3,3.82a.8.8,0,0,0-.17.54V6.73A.68.68,0,0,0,3,7.2a.6.6,0,0,0,.44.18A.53.53,0,0,0,4,7.17a1,1,0,0,0,.12-.5Z"/><path class="cls-2" d="M8.63,8.54V7.91h0a2.24,2.24,0,0,1-.48.52,1.13,1.13,0,0,1-.69.18A1.39,1.39,0,0,1,7,8.54a1.09,1.09,0,0,1-.43-.24,1.32,1.32,0,0,1-.33-.49A2.33,2.33,0,0,1,6.11,7a4.89,4.89,0,0,1,.08-.91,1.51,1.51,0,0,1,.31-.65,1.44,1.44,0,0,1,.59-.38A3.19,3.19,0,0,1,8,4.93h.59V4.33a1,1,0,0,0-.13-.52A.52.52,0,0,0,8,3.61a.71.71,0,0,0-.44.15.78.78,0,0,0-.26.46H6.13A2,2,0,0,1,6.69,2.9a1.73,1.73,0,0,1,.57-.38A2,2,0,0,1,8,2.38a2.18,2.18,0,0,1,.72.12,1.71,1.71,0,0,1,.59.36,2,2,0,0,1,.38.6,2.18,2.18,0,0,1,.14.84V8.54Zm0-2.62-.34,0a1.2,1.2,0,0,0-.67.18.76.76,0,0,0-.29.68.89.89,0,0,0,.17.56A.55.55,0,0,0,8,7.53a.63.63,0,0,0,.49-.2.91.91,0,0,0,.17-.58Z"/><path class="cls-2" d="M13,4.16a.59.59,0,0,0-.2-.47.65.65,0,0,0-.42-.16.59.59,0,0,0-.45.19.66.66,0,0,0-.15.43.8.8,0,0,0,.08.33.85.85,0,0,0,.44.29l.71.29a1.73,1.73,0,0,1,.95.72,2,2,0,0,1,.26,1,1.85,1.85,0,0,1-.52,1.3,1.56,1.56,0,0,1-.58.39,1.88,1.88,0,0,1-2-.32,1.58,1.58,0,0,1-.4-.57,1.81,1.81,0,0,1-.17-.8h1.15a1.11,1.11,0,0,0,.17.47.56.56,0,0,0,.49.22.71.71,0,0,0,.47-.18A.59.59,0,0,0,13,6.8a.69.69,0,0,0-.13-.43,1.08,1.08,0,0,0-.48-.32l-.59-.21a2.08,2.08,0,0,1-.9-.64,1.66,1.66,0,0,1-.33-1,1.89,1.89,0,0,1,.14-.72,1.78,1.78,0,0,1,.4-.57,1.5,1.5,0,0,1,.56-.36,1.82,1.82,0,0,1,.7-.13,1.93,1.93,0,0,1,.69.13,1.6,1.6,0,0,1,.54.38,1.85,1.85,0,0,1,.36.57,1.82,1.82,0,0,1,.13.7Z"/><path class="cls-2" d="M17.2,4.16a.63.63,0,0,0-.2-.47.69.69,0,0,0-.43-.16.55.55,0,0,0-.44.19.62.62,0,0,0-.16.43.68.68,0,0,0,.09.33.81.81,0,0,0,.43.29l.72.29a1.7,1.7,0,0,1,.94.72,2,2,0,0,1,.26,1,1.85,1.85,0,0,1-.52,1.3,1.61,1.61,0,0,1-.57.39,1.81,1.81,0,0,1-.74.15,1.76,1.76,0,0,1-1.24-.47,1.61,1.61,0,0,1-.41-.57,2,2,0,0,1-.17-.8h1.15a1.12,1.12,0,0,0,.18.47.53.53,0,0,0,.48.22.72.72,0,0,0,.48-.18.59.59,0,0,0,.21-.48.69.69,0,0,0-.14-.43,1,1,0,0,0-.48-.32l-.58-.21a2.06,2.06,0,0,1-.91-.64,1.66,1.66,0,0,1-.33-1A1.89,1.89,0,0,1,15,3.44a1.78,1.78,0,0,1,.4-.57,1.58,1.58,0,0,1,.56-.36,1.82,1.82,0,0,1,.7-.13,1.93,1.93,0,0,1,.69.13,1.75,1.75,0,0,1,.55.38,1.85,1.85,0,0,1,.36.57,2,2,0,0,1,.13.7Z"/><path class="cls-2" d="M19.2,8.54V0h1.22V3h0a1.53,1.53,0,0,1,.48-.47,1.39,1.39,0,0,1,.65-.16,1.26,1.26,0,0,1,.69.16,1.35,1.35,0,0,1,.4.39,1.18,1.18,0,0,1,.15.51,7.72,7.72,0,0,1,0,1V6.73a5.56,5.56,0,0,1-.05.8,1.56,1.56,0,0,1-.15.5,1.12,1.12,0,0,1-1.07.58,1.15,1.15,0,0,1-.7-.18A3.79,3.79,0,0,1,20.42,8v.55Zm2.44-4.21a1,1,0,0,0-.13-.51A.5.5,0,0,0,21,3.61a.57.57,0,0,0-.44.18.66.66,0,0,0-.18.48V6.63a.83.83,0,0,0,.17.54.52.52,0,0,0,.45.21.49.49,0,0,0,.45-.22,1.11,1.11,0,0,0,.15-.6Z"/><path class="cls-2" d="M23.76,4.49a4.83,4.83,0,0,1,0-.68A1.55,1.55,0,0,1,24,3.26a1.59,1.59,0,0,1,.62-.64,1.84,1.84,0,0,1,1-.24,1.87,1.87,0,0,1,1,.24,1.59,1.59,0,0,1,.62.64,1.55,1.55,0,0,1,.18.55,4.83,4.83,0,0,1,.05.68v2a4.72,4.72,0,0,1-.05.68,1.55,1.55,0,0,1-.18.55,1.59,1.59,0,0,1-.62.64,1.87,1.87,0,0,1-1,.24,1.84,1.84,0,0,1-1-.24A1.59,1.59,0,0,1,24,7.73a1.55,1.55,0,0,1-.18-.55,4.72,4.72,0,0,1,0-.68ZM25,6.69a.72.72,0,0,0,.17.52.53.53,0,0,0,.43.17A.55.55,0,0,0,26,7.21a.72.72,0,0,0,.16-.52V4.3A.74.74,0,0,0,26,3.78a.55.55,0,0,0-.44-.17.53.53,0,0,0-.43.17A.74.74,0,0,0,25,4.3Z"/><path class="cls-2" d="M28.2,4.49a4.83,4.83,0,0,1,.05-.68,1.55,1.55,0,0,1,.18-.55,1.59,1.59,0,0,1,.62-.64,1.84,1.84,0,0,1,1-.24,1.87,1.87,0,0,1,1,.24,1.59,1.59,0,0,1,.62.64,1.55,1.55,0,0,1,.18.55,4.83,4.83,0,0,1,.05.68v2a4.72,4.72,0,0,1-.05.68,1.55,1.55,0,0,1-.18.55,1.59,1.59,0,0,1-.62.64,1.87,1.87,0,0,1-1,.24,1.84,1.84,0,0,1-1-.24,1.59,1.59,0,0,1-.62-.64,1.55,1.55,0,0,1-.18-.55,4.72,4.72,0,0,1-.05-.68Zm1.22,2.2a.72.72,0,0,0,.17.52.53.53,0,0,0,.43.17.55.55,0,0,0,.44-.17.72.72,0,0,0,.16-.52V4.3a.74.74,0,0,0-.16-.52A.55.55,0,0,0,30,3.61a.53.53,0,0,0-.43.17.74.74,0,0,0-.17.52Z"/><path class="cls-2" d="M32.75,8.54V0H34V5.11h0l1.47-2.66H36.7L35.24,4.93,37,8.54H35.66l-1.1-2.63L34,6.83V8.54Z"/></g></svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,2 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="120px" height="20px" viewBox="15 0 10 10" enable-background="new 0 0 270 10" xml:space="preserve"><defs><style>.cls-1{isolation:isolate;}.cls-2{fill:#000;}</style></defs><g class="cls-1"><path class="cls-2" d="M1.65,11V2.45H2.87V3a2.81,2.81,0,0,1,.47-.45A1.13,1.13,0,0,1,4,2.38,1.11,1.11,0,0,1,5.1,3a1.55,1.55,0,0,1,.16.5,5.61,5.61,0,0,1,0,.81V6.58c0,.45,0,.77,0,1a1.17,1.17,0,0,1-.55.9,1.23,1.23,0,0,1-.7.16,1.35,1.35,0,0,1-.64-.16A1.53,1.53,0,0,1,2.89,8h0v3ZM4.08,4.43a1.21,1.21,0,0,0-.14-.6.51.51,0,0,0-.46-.22A.54.54,0,0,0,3,3.82a.8.8,0,0,0-.17.54V6.73A.68.68,0,0,0,3,7.2a.6.6,0,0,0,.44.18A.53.53,0,0,0,4,7.17a1,1,0,0,0,.12-.5Z"/><path class="cls-2" d="M8.63,8.54V7.91h0a2.24,2.24,0,0,1-.48.52,1.13,1.13,0,0,1-.69.18A1.39,1.39,0,0,1,7,8.54a1.09,1.09,0,0,1-.43-.24,1.32,1.32,0,0,1-.33-.49A2.33,2.33,0,0,1,6.11,7a4.89,4.89,0,0,1,.08-.91,1.51,1.51,0,0,1,.31-.65,1.44,1.44,0,0,1,.59-.38A3.19,3.19,0,0,1,8,4.93h.59V4.33a1,1,0,0,0-.13-.52A.52.52,0,0,0,8,3.61a.71.71,0,0,0-.44.15.78.78,0,0,0-.26.46H6.13A2,2,0,0,1,6.69,2.9a1.73,1.73,0,0,1,.57-.38A2,2,0,0,1,8,2.38a2.18,2.18,0,0,1,.72.12,1.71,1.71,0,0,1,.59.36,2,2,0,0,1,.38.6,2.18,2.18,0,0,1,.14.84V8.54Zm0-2.62-.34,0a1.2,1.2,0,0,0-.67.18.76.76,0,0,0-.29.68.89.89,0,0,0,.17.56A.55.55,0,0,0,8,7.53a.63.63,0,0,0,.49-.2.91.91,0,0,0,.17-.58Z"/><path class="cls-2" d="M13,4.16a.59.59,0,0,0-.2-.47.65.65,0,0,0-.42-.16.59.59,0,0,0-.45.19.66.66,0,0,0-.15.43.8.8,0,0,0,.08.33.85.85,0,0,0,.44.29l.71.29a1.73,1.73,0,0,1,.95.72,2,2,0,0,1,.26,1,1.85,1.85,0,0,1-.52,1.3,1.56,1.56,0,0,1-.58.39,1.88,1.88,0,0,1-2-.32,1.58,1.58,0,0,1-.4-.57,1.81,1.81,0,0,1-.17-.8h1.15a1.11,1.11,0,0,0,.17.47.56.56,0,0,0,.49.22.71.71,0,0,0,.47-.18A.59.59,0,0,0,13,6.8a.69.69,0,0,0-.13-.43,1.08,1.08,0,0,0-.48-.32l-.59-.21a2.08,2.08,0,0,1-.9-.64,1.66,1.66,0,0,1-.33-1,1.89,1.89,0,0,1,.14-.72,1.78,1.78,0,0,1,.4-.57,1.5,1.5,0,0,1,.56-.36,1.82,1.82,0,0,1,.7-.13,1.93,1.93,0,0,1,.69.13,1.6,1.6,0,0,1,.54.38,1.85,1.85,0,0,1,.36.57,1.82,1.82,0,0,1,.13.7Z"/><path class="cls-2" d="M17.2,4.16a.63.63,0,0,0-.2-.47.69.69,0,0,0-.43-.16.55.55,0,0,0-.44.19.62.62,0,0,0-.16.43.68.68,0,0,0,.09.33.81.81,0,0,0,.43.29l.72.29a1.7,1.7,0,0,1,.94.72,2,2,0,0,1,.26,1,1.85,1.85,0,0,1-.52,1.3,1.61,1.61,0,0,1-.57.39,1.81,1.81,0,0,1-.74.15,1.76,1.76,0,0,1-1.24-.47,1.61,1.61,0,0,1-.41-.57,2,2,0,0,1-.17-.8h1.15a1.12,1.12,0,0,0,.18.47.53.53,0,0,0,.48.22.72.72,0,0,0,.48-.18.59.59,0,0,0,.21-.48.69.69,0,0,0-.14-.43,1,1,0,0,0-.48-.32l-.58-.21a2.06,2.06,0,0,1-.91-.64,1.66,1.66,0,0,1-.33-1A1.89,1.89,0,0,1,15,3.44a1.78,1.78,0,0,1,.4-.57,1.58,1.58,0,0,1,.56-.36,1.82,1.82,0,0,1,.7-.13,1.93,1.93,0,0,1,.69.13,1.75,1.75,0,0,1,.55.38,1.85,1.85,0,0,1,.36.57,2,2,0,0,1,.13.7Z"/><path class="cls-2" d="M19.2,8.54V0h1.22V3h0a1.53,1.53,0,0,1,.48-.47,1.39,1.39,0,0,1,.65-.16,1.26,1.26,0,0,1,.69.16,1.35,1.35,0,0,1,.4.39,1.18,1.18,0,0,1,.15.51,7.72,7.72,0,0,1,0,1V6.73a5.56,5.56,0,0,1-.05.8,1.56,1.56,0,0,1-.15.5,1.12,1.12,0,0,1-1.07.58,1.15,1.15,0,0,1-.7-.18A3.79,3.79,0,0,1,20.42,8v.55Zm2.44-4.21a1,1,0,0,0-.13-.51A.5.5,0,0,0,21,3.61a.57.57,0,0,0-.44.18.66.66,0,0,0-.18.48V6.63a.83.83,0,0,0,.17.54.52.52,0,0,0,.45.21.49.49,0,0,0,.45-.22,1.11,1.11,0,0,0,.15-.6Z"/><path class="cls-2" d="M23.76,4.49a4.83,4.83,0,0,1,0-.68A1.55,1.55,0,0,1,24,3.26a1.59,1.59,0,0,1,.62-.64,1.84,1.84,0,0,1,1-.24,1.87,1.87,0,0,1,1,.24,1.59,1.59,0,0,1,.62.64,1.55,1.55,0,0,1,.18.55,4.83,4.83,0,0,1,.05.68v2a4.72,4.72,0,0,1-.05.68,1.55,1.55,0,0,1-.18.55,1.59,1.59,0,0,1-.62.64,1.87,1.87,0,0,1-1,.24,1.84,1.84,0,0,1-1-.24A1.59,1.59,0,0,1,24,7.73a1.55,1.55,0,0,1-.18-.55,4.72,4.72,0,0,1,0-.68ZM25,6.69a.72.72,0,0,0,.17.52.53.53,0,0,0,.43.17A.55.55,0,0,0,26,7.21a.72.72,0,0,0,.16-.52V4.3A.74.74,0,0,0,26,3.78a.55.55,0,0,0-.44-.17.53.53,0,0,0-.43.17A.74.74,0,0,0,25,4.3Z"/><path class="cls-2" d="M28.2,4.49a4.83,4.83,0,0,1,.05-.68,1.55,1.55,0,0,1,.18-.55,1.59,1.59,0,0,1,.62-.64,1.84,1.84,0,0,1,1-.24,1.87,1.87,0,0,1,1,.24,1.59,1.59,0,0,1,.62.64,1.55,1.55,0,0,1,.18.55,4.83,4.83,0,0,1,.05.68v2a4.72,4.72,0,0,1-.05.68,1.55,1.55,0,0,1-.18.55,1.59,1.59,0,0,1-.62.64,1.87,1.87,0,0,1-1,.24,1.84,1.84,0,0,1-1-.24,1.59,1.59,0,0,1-.62-.64,1.55,1.55,0,0,1-.18-.55,4.72,4.72,0,0,1-.05-.68Zm1.22,2.2a.72.72,0,0,0,.17.52.53.53,0,0,0,.43.17.55.55,0,0,0,.44-.17.72.72,0,0,0,.16-.52V4.3a.74.74,0,0,0-.16-.52A.55.55,0,0,0,30,3.61a.53.53,0,0,0-.43.17.74.74,0,0,0-.17.52Z"/><path class="cls-2" d="M32.75,8.54V0H34V5.11h0l1.47-2.66H36.7L35.24,4.93,37,8.54H35.66l-1.1-2.63L34,6.83V8.54Z"/></g></svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
docs/images/screen_apps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

View File

@ -1,31 +1,16 @@
# Welcome
#
![passbook logo](images/logo.svg){: style="height:50px"}
![passbook brand](images/brand.svg){: style="height:50px"}
Welcome to the passbook Documentation. passbook is an open-source Identity Provider and Usermanagement software. It can be used as a central directory for users or customers and it can integrate with your existing Directory.
## What is passbook?
passbook can also be used as part of an Application to facilitate User Enrollment, Password recovery and Social Login.
passbook is an open-source Identity Provider focused on flexibility and versatility. You can use passbook in an existing environment to add support for new protocols. passbook is also a great solution for implementing signup/recovery/etc in your application, so you don't have to deal with it.
passbook uses the following Terminology:
## Installation
### Policy
See [Docker-compose](installation/docker-compose.md) or [Kubernetes](installation/kubernetes.md)
A Policy is at a base level a yes/no gate. It will either evaluate to True or False depending on the Policy Kind and settings. For example, a "Group Membership Policy" evaluates to True if the User is member of the specified Group and False if not. This can be used to conditionally apply Factors and grant/deny access.
## Screenshots
### Provider
A Provider is a way for other Applications to authenticate against passbook. Common Providers are OpenID Connect (OIDC) and SAML.
### Source
Sources are ways to get users into passbook. This might be an LDAP Connection to import Users from Active Directory, or an OAuth2 Connection to allow Social Logins.
### Application
An application links together Policies with a Provider, allowing you to control access. It also holds Information like UI Name, Icon and more.
### Factors
Factors represent Authentication Factors, like a Password or OTP. These Factors can be dynamically enabled using policies. This allows you to, for example, force users from a certain IP ranges to complete a Captcha to authenticate.
### Property Mappings
Property Mappings allow you to make Information available for external Applications. For example, if you want to login to AWS with passbook, you'd use Property Mappings to set the User's Roles based on their Groups.
![](images/screen_apps.png)
![](images/screen_admin.png)

View File

@ -1,6 +0,0 @@
# Installation
There are two supported ways to install passbook:
- [docker-compose](docker-compose.md) for test- or small productive setups
- [Kubernetes](./kubernetes.md) for larger Productive setups

View File

@ -1,3 +1,61 @@
# Kubernetes
For a mid to high-load Installation, Kubernetes is recommended. passbook is installed using a helm-chart.
```
# Default values for passbook.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
# passbook version to use. Defaults to latest stable version
# image:
# tag:
nameOverride: ""
config:
# Optionally specify fixed secret_key, otherwise generated automatically
# secret_key: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
# Enable error reporting
error_reporting: false
# Log level used by web and worker
# Can be either debug, info, warning, error
log_level: warning
# This Helm chart ships with built-in Prometheus ServiceMonitors and Rules.
# This requires the CoreOS Prometheus Operator.
monitoring:
enabled: false
# Enable Database Backups to S3
# backup:
# access_key: access-key
# secret_key: secret-key
# bucket: s3-bucket
# host: s3-host
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- passbook.k8s.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - passbook.k8s.local
# These settings configure the packaged PostgreSQL and Redis chart.
postgresql:
postgresqlDatabase: passbook
redis:
cluster:
enabled: false
master:
persistence:
enabled: false
# https://stackoverflow.com/a/59189742
disableCommands: []
```

View File

@ -1,33 +0,0 @@
---
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: passbook-docs
namespace: prod-passbook-docs
labels:
app.kubernetes.io/name: passbook-docs
app.kubernetes.io/managed-by: passbook-docs
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: passbook-docs
template:
metadata:
labels:
app.kubernetes.io/name: passbook-docs
spec:
containers:
- name: passbook-docs
image: "beryju/passbook-docs:latest"
ports:
- name: http
containerPort: 80
protocol: TCP
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi

View File

@ -1,21 +0,0 @@
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
labels:
app.kubernetes.io/name: passbook-docs
name: passbook-docs
namespace: prod-passbook-docs
spec:
rules:
- host: docs.passbook.beryju.org
http:
paths:
- backend:
serviceName: passbook-docs-http
servicePort: http
path: /
tls:
- hosts:
- docs.passbook.beryju.org
secretName: passbook-docs-acme

View File

@ -1,17 +0,0 @@
---
apiVersion: v1
kind: Service
metadata:
name: passbook-docs-http
namespace: prod-passbook-docs
labels:
app.kubernetes.io/name: passbook-docs
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app.kubernetes.io/name: passbook-docs

View File

@ -0,0 +1,30 @@
# Expression Policies
!!! notice
These variables are available in addition to the common variables/functions defined in [**Expressions**](../expressions/index.md)
The passing of the policy is determined by the return value of the code. Use `return True` to pass a policy and `return False` to fail it.
### Available Functions
#### `pb_message(message: str)`
Add a message, visible by the end user. This can be used to show the reason why they were denied.
Example:
```python
pb_message("Access denied")
return False
```
### Context variables
- `request`: A PolicyRequest object, which has the following properties:
- `request.user`: The current User, which the Policy is applied against. ([ref](../expressions/reference/user-object.md))
- `request.http_request`: The Django HTTP Request. ([ref](https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects))
- `request.obj`: A Django Model instance. This is only set if the Policy is ran against an object.
- `request.context`: A dictionary with dynamic data. This depends on the origin of the execution.
- `pb_is_sso_flow`: Boolean which is true if request was initiated by authenticating through an external Provider.
- `pb_client_ip`: Client's IP Address or '255.255.255.255' if no IP Address could be extracted.
- `pb_flow_plan`: Current Plan if Policy is called from the Flow Planner.

View File

@ -1,19 +0,0 @@
# Expression Policy
Expression Policies allows you to write custom Policy Logic using Jinja2 Templating language.
For a language reference, see [here](https://jinja.palletsprojects.com/en/2.11.x/templates/).
The following objects are passed into the variable:
- `request`: A PolicyRequest object, which has the following properties:
- `request.user`: The current User, which the Policy is applied against. ([ref](../../property-mappings/reference/user-object.md))
- `request.http_request`: The Django HTTP Request, as documented [here](https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects).
- `request.obj`: A Django Model instance. This is only set if the Policy is ran against an object.
- `pb_is_sso_flow`: Boolean which is true if request was initiated by authenticating through an external Provider.
- `pb_is_group_member(user, group_name)`: Function which checks if `user` is member of a Group with Name `gorup_name`.
There are also the following custom filters available:
- `regex_match(regex)`: Return True if value matches `regex`
- `regex_replace(regex, repl)`: Replace string matched by `regex` with `repl`

View File

@ -8,10 +8,6 @@ There are two different Kind of policies, a Standard Policy and a Password Polic
---
### Group-Membership Policy
This policy evaluates to True if the current user is a Member of the selected group.
### Reputation Policy
passbook keeps track of failed login attempts by Source IP and Attempted Username. These values are saved as scores. Each failed login decreases the Score for the Client IP as well as the targeted Username by one.
@ -20,11 +16,7 @@ This policy can be used to for example prompt Clients with a low score to pass a
## Expression Policy
See [Expression Policy](expression/index.md).
### Webhook Policy
This policy allows you to send an arbitrary HTTP Request to any URL. You can then use JSONPath to extract the result you need.
See [Expression Policy](expression.md).
## Password Policies
@ -35,11 +27,11 @@ This policy allows you to send an arbitrary HTTP Request to any URL. You can the
This Policy allows you to specify Password rules, like Length and required Characters.
The following rules can be set:
- Minimum amount of Uppercase Characters
- Minimum amount of Lowercase Characters
- Minimum amount of Symbols Characters
- Minimum Length
- Symbol charset (define which characters are counted as symbols)
- Minimum amount of Uppercase Characters
- Minimum amount of Lowercase Characters
- Minimum amount of Symbols Characters
- Minimum Length
- Symbol charset (define which characters are counted as symbols)
### Have I Been Pwned Policy

View File

@ -0,0 +1,12 @@
# Property Mapping Expressions
The property mapping should return a value that is expected by the Provider/Source. What types are supported, is documented in the individual Provider/Source. Returning `None` is always accepted, this simply skips this mapping.
!!! notice
These variables are available in addition to the common variables/functions defined in [**Expressions**](../expressions/index.md)
### Context Variables
- `user`: The current user, this might be `None` if there is no contextual user. ([ref](../expressions/reference/user-object.md))
- `request`: The current request, this might be `None` if there is no contextual request. ([ref](https://docs.djangoproject.com/en/3.0/ref/request-response/#httprequest-objects))
- Arbitrary other arguments given by the provider, this is documented on the Provider/Source.

View File

@ -12,10 +12,10 @@ You can find examples [here](integrations/)
LDAP Property Mappings are used when you define a LDAP Source. These Mappings define which LDAP Property maps to which passbook Property. By default, these mappings are created:
- Autogenerated LDAP Mapping: givenName -> first_name
- Autogenerated LDAP Mapping: mail -> email
- Autogenerated LDAP Mapping: name -> name
- Autogenerated LDAP Mapping: sAMAccountName -> username
- Autogenerated LDAP Mapping: sn -> last_name
- Autogenerated LDAP Mapping: givenName -> first_name
- Autogenerated LDAP Mapping: mail -> email
- Autogenerated LDAP Mapping: name -> name
- Autogenerated LDAP Mapping: sAMAccountName -> username
- Autogenerated LDAP Mapping: sn -> last_name
These are configured for the most common LDAP Setups.

2
docs/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
mkdocs
mkdocs-material

1
docs/runtime.txt Normal file
View File

@ -0,0 +1 @@
3.7

27
docs/terminology.md Normal file
View File

@ -0,0 +1,27 @@
### Policy
A Policy is at a base level a yes/no gate. It will either evaluate to True or False depending on the Policy Kind and settings. For example, a "Group Membership Policy" evaluates to True if the User is member of the specified Group and False if not. This can be used to conditionally apply Stages, grant/deny access to various objects and is also used for other custom logic.
### Provider
A Provider is a way for other Applications to authenticate against passbook. Common Providers are OpenID Connect (OIDC) and SAML.
### Source
Sources are ways to get users into passbook. This might be an LDAP Connection to import Users from Active Directory, or an OAuth2 Connection to allow Social Logins.
### Application
An application links together Policies with a Provider, allowing you to control access. It also holds Information like UI Name, Icon and more.
### Flows
Flows are a method of describing a sequence of stages. These flows can be used to defined how a user authenticates, enrolls, etc.
### Stages
A stage represents a single verification or logic step. They are used to authenticate users, enroll them, and more. These stages can optionally be applied to a flow via policies.
### Property Mappings
Property Mappings allow you to make Information available for external Applications. For example, if you want to login to AWS with passbook, you'd use Property Mappings to set the User's Roles based on their Groups.

View File

@ -0,0 +1,33 @@
# Upgrading from 0.8.x
Due to some database changes that had to be rather sooner than later, there is no possibility to directly upgrade. You must extract the data before hand and import it again. It is recommended to spin up a second instance of passbook to do this.
To export data from your old instance, run this command:
(with docker-compose)
```
docker-compose exec server ./manage.py dumpdata -o /tmp/passbook_dump.json passbook_core.User passbook_core.Group passbook_crypto.CertificateKeyPair passbook_audit.Event
docker cp passbook_server_1:/tmp/passbook_dump.json passbook_dump.json
```
(with kubernetes)
```
kubectl exec -it passbook-web-... -- ./manage.py dumpdata -o /tmp/passbook_dump.json passbook_core.User passbook_core.Group passbook_crypto.CertificateKeyPair passbook_audit.Event
kubectl cp passbook-web-...:/tmp/passbook_dump.json passbook_dump.json
```
After that, create a new passbook instance in a different namespace (kubernetes) or in a different folder (docker-compose). Once this instance is running, you can use the following commands to restore the data. On docker-compose, you still have to run the `migrate` command, to create all database structures.
(docker-compose)
```
docker cp passbook_dump.json new_passbook_server_1:/tmp/passbook_dump.json
docker-compose exec server ./manage.py loaddata /tmp/passbook_dump.json
```
(with kubernetes)
```
kubectl cp passbook_dump.json passbook-web-...:/tmp/passbook_dump.json
kubectl exec -it passbook-web-... -- ./manage.py loaddata /tmp/passbook_dump.json
```
Now, you should be able to login to the new passbook instance, and migrate the rest of the data over.

View File

@ -1,6 +1,6 @@
apiVersion: v1
appVersion: "0.8.0-beta"
appVersion: "0.9.0-pre1"
description: A Helm chart for passbook.
name: passbook
version: "0.8.0-beta"
version: "0.9.0-pre1"
icon: https://git.beryju.org/uploads/-/system/project/avatar/108/logo.png

View File

@ -8,9 +8,16 @@ data:
host: "{{ .Release.Name }}-postgresql"
name: "{{ .Values.postgresql.postgresqlDatabase }}"
user: postgres
{{- if .Values.backup }}
backup:
access_key: "{{ .Values.backup.access_key }}"
secret_key: "{{ .Values.backup.secret_key }}"
bucket: "{{ .Values.backup.bucket }}"
host: "{{ .Values.backup.host }}"
{{- end}}
redis:
host: "{{ .Release.Name }}-redis-master"
cache_db: 0
message_queue_db: 1
error_reporting: {{ .Values.config.error_reporting }}
domain: ".{{ index .Values.ingress.hosts 0 }}"
log_level: "{{ .Values.config.log_level }}"

View File

@ -7,11 +7,13 @@ metadata:
helm.sh/chart: {{ include "passbook.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
k8s.passbook.io/component: static
spec:
selector:
matchLabels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
k8s.passbook.io/component: static
template:
metadata:
labels:
@ -25,19 +27,19 @@ spec:
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 8080
containerPort: 80
protocol: TCP
livenessProbe:
initialDelaySeconds: 10
timeoutSeconds: 5
httpGet:
path: /-/ping
path: /
port: http
readinessProbe:
initialDelaySeconds: 10
timeoutSeconds: 5
httpGet:
path: /-/ping
path: /
port: http
resources:
requests:

View File

@ -11,7 +11,7 @@ metadata:
spec:
type: ClusterIP
ports:
- port: 8080
- port: 80
targetPort: http
protocol: TCP
name: http

View File

@ -7,18 +7,20 @@ metadata:
helm.sh/chart: {{ include "passbook.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
k8s.passbook.io/component: web
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
k8s.passbook.io/component: web
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
passbook.io/component: web
k8s.passbook.io/component: web
spec:
volumes:
- name: config-volume
@ -27,9 +29,9 @@ spec:
initContainers:
- name: passbook-database-migrations
image: "beryju/passbook:{{ .Values.image.tag }}"
command:
- ./manage.py
imagePullPolicy: Always
args:
- ./manage.py
- migrate
volumeMounts:
- mountPath: /etc/passbook
@ -57,10 +59,9 @@ spec:
containers:
- name: {{ .Chart.Name }}
image: "beryju/passbook:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent
command:
- uwsgi
imagePullPolicy: Always
args:
- uwsgi
- uwsgi.ini
volumeMounts:
- mountPath: /etc/passbook

View File

@ -18,4 +18,4 @@ spec:
selector:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
passbook.io/component: web
k8s.passbook.io/component: web

View File

@ -7,18 +7,20 @@ metadata:
helm.sh/chart: {{ include "passbook.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
k8s.passbook.io/component: worker
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
k8s.passbook.io/component: worker
template:
metadata:
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
passbook.io/component: worker
k8s.passbook.io/component: worker
spec:
volumes:
- name: config-volume
@ -28,9 +30,8 @@ spec:
- name: {{ .Chart.Name }}
image: "beryju/passbook:{{ .Values.image.tag }}"
imagePullPolicy: IfNotPresent
command:
- celery
args:
- celery
- worker
- --autoscale=10,3
- -E

View File

@ -2,7 +2,7 @@
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
image:
tag: 0.8.0-beta
tag: 0.9.0-pre1
nameOverride: ""
@ -11,14 +11,22 @@ config:
# secret_key: _k*@6h2u2@q-dku57hhgzb7tnx*ba9wodcb^s9g0j59@=y(@_o
# Enable error reporting
error_reporting: false
email:
host: localhost
# Log level used by web and worker
# Can be either debug, info, warning, error
log_level: warning
# This Helm chart ships with built-in Prometheus ServiceMonitors and Rules.
# This requires the CoreOS Prometheus Operator.
monitoring:
enabled: false
# Enable Database Backups to S3
# backup:
# access_key: access-key
# secret_key: secret-key
# bucket: s3-bucket
# host: s3-host
ingress:
enabled: false
annotations: {}
@ -42,3 +50,5 @@ redis:
master:
persistence:
enabled: false
# https://stackoverflow.com/a/59189742
disableCommands: []

View File

@ -2,9 +2,12 @@
"""Django manage.py"""
import os
import sys
from defusedxml import defuse_stdlib
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'passbook.root.settings')
defuse_stdlib()
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "passbook.root.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:

View File

@ -1,39 +1,73 @@
site_name: passbook Docs
site_url: https://beryju.github.io/passbook
site_url: https://passbook.beryju.org/
copyright: "Copyright &copy; 2019 - 2020 BeryJu.org"
nav:
- Home: index.md
- Terminology: terminology.md
- Installation:
- Installation: installation/install.md
- docker-compose: installation/docker-compose.md
- Kubernetes: installation/kubernetes.md
- docker-compose: installation/docker-compose.md
- Kubernetes: installation/kubernetes.md
- Flows:
Overview: flow/flows.md
Examples:
- Login: flow/examples/login.md
- Stages:
- Captcha Stage: flow/stages/captcha/index.md
- Dummy Stage: flow/stages/dummy/index.md
- E-Mail Stage: flow/stages/email/index.md
- Identification Stage: flow/stages/identification/index.md
- Invitation Stage: flow/stages/invitation/index.md
- OTP Stage: flow/stages/otp/index.md
- Password Stage: flow/stages/password/index.md
- Prompt Stage: flow/stages/prompt/index.md
- Prompt Stage Validation: flow/stages/prompt/validation.md
- User Delete Stage: flow/stages/user_delete.md
- User Login Stage: flow/stages/user_login.md
- User Logout Stage: flow/stages/user_logout.md
- User Write Stage: flow/stages/user_write.md
- Sources: sources.md
- Providers: providers.md
- Expressions:
- Overview: expressions/index.md
- Reference:
- User Object: expressions/reference/user-object.md
- Property Mappings:
- Overview: property-mappings/index.md
- Reference:
- User Object: property-mappings/reference/user-object.md
- Factors: factors.md
- Overview: property-mappings/index.md
- Expressions: property-mappings/expression.md
- Policies:
- Overview: policies/index.md
- Expression: policies/expression/index.md
- Overview: policies/index.md
- Expression: policies/expression.md
- Integrations:
- as Provider:
- Amazon Web Services: integrations/services/aws/index.md
- GitLab: integrations/services/gitlab/index.md
- Rancher: integrations/services/rancher/index.md
- Harbor: integrations/services/harbor/index.md
- Sentry: integrations/services/sentry/index.md
- Ansible Tower/AWX: integrations/services/tower-awx/index.md
- as Provider:
- Amazon Web Services: integrations/services/aws/index.md
- GitLab: integrations/services/gitlab/index.md
- Rancher: integrations/services/rancher/index.md
- Harbor: integrations/services/harbor/index.md
- Sentry: integrations/services/sentry/index.md
- Ansible Tower/AWX: integrations/services/tower-awx/index.md
- Upgrading from v0.8.x: upgrading-from-0.8.x.md
repo_name: "BeryJu.org/passbook"
repo_name: "BeryJu/passbook"
repo_url: https://github.com/BeryJu/passbook
theme:
name: "material"
logo: "images/logo.svg"
name: material
logo: images/logo.svg
favicon: images/logo.svg
palette:
scheme: slate
primary: white
markdown_extensions:
- toc:
permalink: "¶"
- admonition
- codehilite
- pymdownx.betterem:
smart_enable: all
- pymdownx.inlinehilite
- pymdownx.magiclink
- attr_list
plugins:
- search

View File

@ -1,2 +1,2 @@
"""passbook"""
__version__ = "0.8.0-beta"
__version__ = "0.9.0-pre1"

View File

@ -4,6 +4,27 @@ from django import forms
from django.utils.translation import gettext_lazy as _
class CodeMirrorWidget(forms.Textarea):
"""Custom Textarea-based Widget that triggers a CodeMirror editor"""
# CodeMirror mode to enable
mode: str
def __init__(self, *args, mode="yaml", **kwargs):
super().__init__(*args, **kwargs)
self.mode = mode
def render(self, *args, **kwargs):
if "attrs" not in kwargs:
kwargs["attrs"] = {}
attrs = kwargs["attrs"]
if "class" not in attrs:
attrs["class"] = ""
attrs["class"] += " codemirror"
attrs["data-cm-mode"] = self.mode
return super().render(*args, **kwargs)
class InvalidYAMLInput(str):
"""Invalid YAML String type"""
@ -48,7 +69,7 @@ class YAMLField(forms.CharField):
def prepare_value(self, value):
if isinstance(value, InvalidYAMLInput):
return value
return yaml.dump(value, explicit_start=True)
return yaml.dump(value, explicit_start=True, default_flow_style=False)
def has_changed(self, initial, data):
if super().has_changed(initial, data):

View File

@ -1,40 +0,0 @@
"""passbook form helpers"""
from django import forms
from passbook.admin.fields import YAMLField
class TagModelForm(forms.ModelForm):
"""Base form for models that have attributes"""
def __init__(self, *args, **kwargs):
# Check if we have an instance, load tags otherwise use an empty dict
instance = kwargs.get("instance", None)
tags = instance.tags if instance else {}
# Make sure all predefined tags exist in tags, and set default if they don't
predefined_tags = (
self._meta.model().get_predefined_tags() # pylint: disable=no-member
)
for key, value in predefined_tags.items():
if key not in tags:
tags[key] = value
# Format JSON
kwargs["initial"]["tags"] = tags
super().__init__(*args, **kwargs)
def clean_tags(self):
"""Make sure all required tags are set"""
if hasattr(self.instance, "get_required_keys") and hasattr(
self.instance, "tags"
):
for key in self.instance.get_required_keys():
if key not in self.cleaned_data.get("tags"):
raise forms.ValidationError("Tag %s missing." % key)
return self.cleaned_data.get("tags")
# pylint: disable=too-few-public-methods
class TagModelFormMeta:
"""Base Meta class that uses the YAMLField"""
field_classes = {"tags": YAMLField}

View File

@ -1,6 +1,7 @@
"""passbook administration forms"""
from django import forms
from passbook.admin.fields import CodeMirrorWidget, YAMLField
from passbook.core.models import User
@ -8,3 +9,4 @@ class PolicyTestForm(forms.Form):
"""Form to test policies against user"""
user = forms.ModelChoiceField(queryset=User.objects.all())
context = YAMLField(widget=CodeMirrorWidget(), required=False, initial=dict)

View File

@ -1,7 +1,17 @@
"""passbook core source form fields"""
# from django import forms
SOURCE_FORM_FIELDS = ["name", "slug", "enabled", "policies"]
SOURCE_SERIALIZER_FIELDS = ["pk", "name", "slug", "enabled", "policies"]
# class SourceForm(forms.Form)
SOURCE_FORM_FIELDS = [
"name",
"slug",
"enabled",
"authentication_flow",
"enrollment_flow",
]
SOURCE_SERIALIZER_FIELDS = [
"pk",
"name",
"slug",
"enabled",
"authentication_flow",
"enrollment_flow",
]

View File

@ -2,7 +2,7 @@
from django import forms
from passbook.admin.fields import YAMLField
from passbook.admin.fields import CodeMirrorWidget, YAMLField
from passbook.core.models import User
@ -15,6 +15,7 @@ class UserForm(forms.ModelForm):
fields = ["username", "name", "email", "is_staff", "is_active", "attributes"]
widgets = {
"name": forms.TextInput,
"attributes": CodeMirrorWidget,
}
field_classes = {
"attributes": YAMLField,

View File

@ -1,209 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var noOptions = {};
var nonWS = /[^\s\u00a0]/;
var Pos = CodeMirror.Pos;
function firstNonWS(str) {
var found = str.search(nonWS);
return found == -1 ? 0 : found;
}
CodeMirror.commands.toggleComment = function(cm) {
cm.toggleComment();
};
CodeMirror.defineExtension("toggleComment", function(options) {
if (!options) options = noOptions;
var cm = this;
var minLine = Infinity, ranges = this.listSelections(), mode = null;
for (var i = ranges.length - 1; i >= 0; i--) {
var from = ranges[i].from(), to = ranges[i].to();
if (from.line >= minLine) continue;
if (to.line >= minLine) to = Pos(minLine, 0);
minLine = from.line;
if (mode == null) {
if (cm.uncomment(from, to, options)) mode = "un";
else { cm.lineComment(from, to, options); mode = "line"; }
} else if (mode == "un") {
cm.uncomment(from, to, options);
} else {
cm.lineComment(from, to, options);
}
}
});
// Rough heuristic to try and detect lines that are part of multi-line string
function probablyInsideString(cm, pos, line) {
return /\bstring\b/.test(cm.getTokenTypeAt(Pos(pos.line, 0))) && !/^[\'\"\`]/.test(line)
}
function getMode(cm, pos) {
var mode = cm.getMode()
return mode.useInnerComments === false || !mode.innerMode ? mode : cm.getModeAt(pos)
}
CodeMirror.defineExtension("lineComment", function(from, to, options) {
if (!options) options = noOptions;
var self = this, mode = getMode(self, from);
var firstLine = self.getLine(from.line);
if (firstLine == null || probablyInsideString(self, from, firstLine)) return;
var commentString = options.lineComment || mode.lineComment;
if (!commentString) {
if (options.blockCommentStart || mode.blockCommentStart) {
options.fullLines = true;
self.blockComment(from, to, options);
}
return;
}
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line + 1 : to.line, self.lastLine() + 1);
var pad = options.padding == null ? " " : options.padding;
var blankLines = options.commentBlankLines || from.line == to.line;
self.operation(function() {
if (options.indent) {
var baseString = null;
for (var i = from.line; i < end; ++i) {
var line = self.getLine(i);
var whitespace = line.slice(0, firstNonWS(line));
if (baseString == null || baseString.length > whitespace.length) {
baseString = whitespace;
}
}
for (var i = from.line; i < end; ++i) {
var line = self.getLine(i), cut = baseString.length;
if (!blankLines && !nonWS.test(line)) continue;
if (line.slice(0, cut) != baseString) cut = firstNonWS(line);
self.replaceRange(baseString + commentString + pad, Pos(i, 0), Pos(i, cut));
}
} else {
for (var i = from.line; i < end; ++i) {
if (blankLines || nonWS.test(self.getLine(i)))
self.replaceRange(commentString + pad, Pos(i, 0));
}
}
});
});
CodeMirror.defineExtension("blockComment", function(from, to, options) {
if (!options) options = noOptions;
var self = this, mode = getMode(self, from);
var startString = options.blockCommentStart || mode.blockCommentStart;
var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) {
if ((options.lineComment || mode.lineComment) && options.fullLines != false)
self.lineComment(from, to, options);
return;
}
if (/\bcomment\b/.test(self.getTokenTypeAt(Pos(from.line, 0)))) return
var end = Math.min(to.line, self.lastLine());
if (end != from.line && to.ch == 0 && nonWS.test(self.getLine(end))) --end;
var pad = options.padding == null ? " " : options.padding;
if (from.line > end) return;
self.operation(function() {
if (options.fullLines != false) {
var lastLineHasText = nonWS.test(self.getLine(end));
self.replaceRange(pad + endString, Pos(end));
self.replaceRange(startString + pad, Pos(from.line, 0));
var lead = options.blockCommentLead || mode.blockCommentLead;
if (lead != null) for (var i = from.line + 1; i <= end; ++i)
if (i != end || lastLineHasText)
self.replaceRange(lead + pad, Pos(i, 0));
} else {
self.replaceRange(endString, to);
self.replaceRange(startString, from);
}
});
});
CodeMirror.defineExtension("uncomment", function(from, to, options) {
if (!options) options = noOptions;
var self = this, mode = getMode(self, from);
var end = Math.min(to.ch != 0 || to.line == from.line ? to.line : to.line - 1, self.lastLine()), start = Math.min(from.line, end);
// Try finding line comments
var lineString = options.lineComment || mode.lineComment, lines = [];
var pad = options.padding == null ? " " : options.padding, didSomething;
lineComment: {
if (!lineString) break lineComment;
for (var i = start; i <= end; ++i) {
var line = self.getLine(i);
var found = line.indexOf(lineString);
if (found > -1 && !/comment/.test(self.getTokenTypeAt(Pos(i, found + 1)))) found = -1;
if (found == -1 && nonWS.test(line)) break lineComment;
if (found > -1 && nonWS.test(line.slice(0, found))) break lineComment;
lines.push(line);
}
self.operation(function() {
for (var i = start; i <= end; ++i) {
var line = lines[i - start];
var pos = line.indexOf(lineString), endPos = pos + lineString.length;
if (pos < 0) continue;
if (line.slice(endPos, endPos + pad.length) == pad) endPos += pad.length;
didSomething = true;
self.replaceRange("", Pos(i, pos), Pos(i, endPos));
}
});
if (didSomething) return true;
}
// Try block comments
var startString = options.blockCommentStart || mode.blockCommentStart;
var endString = options.blockCommentEnd || mode.blockCommentEnd;
if (!startString || !endString) return false;
var lead = options.blockCommentLead || mode.blockCommentLead;
var startLine = self.getLine(start), open = startLine.indexOf(startString)
if (open == -1) return false
var endLine = end == start ? startLine : self.getLine(end)
var close = endLine.indexOf(endString, end == start ? open + startString.length : 0);
var insideStart = Pos(start, open + 1), insideEnd = Pos(end, close + 1)
if (close == -1 ||
!/comment/.test(self.getTokenTypeAt(insideStart)) ||
!/comment/.test(self.getTokenTypeAt(insideEnd)) ||
self.getRange(insideStart, insideEnd, "\n").indexOf(endString) > -1)
return false;
// Avoid killing block comments completely outside the selection.
// Positions of the last startString before the start of the selection, and the first endString after it.
var lastStart = startLine.lastIndexOf(startString, from.ch);
var firstEnd = lastStart == -1 ? -1 : startLine.slice(0, from.ch).indexOf(endString, lastStart + startString.length);
if (lastStart != -1 && firstEnd != -1 && firstEnd + endString.length != from.ch) return false;
// Positions of the first endString after the end of the selection, and the last startString before it.
firstEnd = endLine.indexOf(endString, to.ch);
var almostLastStart = endLine.slice(to.ch).lastIndexOf(startString, firstEnd - to.ch);
lastStart = (firstEnd == -1 || almostLastStart == -1) ? -1 : to.ch + almostLastStart;
if (firstEnd != -1 && lastStart != -1 && lastStart != to.ch) return false;
self.operation(function() {
self.replaceRange("", Pos(end, close - (pad && endLine.slice(close - pad.length, close) == pad ? pad.length : 0)),
Pos(end, close + endString.length));
var openEnd = open + startString.length;
if (pad && startLine.slice(openEnd, openEnd + pad.length) == pad) openEnd += pad.length;
self.replaceRange("", Pos(start, open), Pos(start, openEnd));
if (lead) for (var i = start + 1; i <= end; ++i) {
var line = self.getLine(i), found = line.indexOf(lead);
if (found == -1 || nonWS.test(line.slice(0, found))) continue;
var foundEnd = found + lead.length;
if (pad && line.slice(foundEnd, foundEnd + pad.length) == pad) foundEnd += pad.length;
self.replaceRange("", Pos(i, found), Pos(i, foundEnd));
}
});
return true;
});
});

View File

@ -1,78 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
function continueComment(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), mode, inserts = [];
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].head
if (!/\bcomment\b/.test(cm.getTokenTypeAt(pos))) return CodeMirror.Pass;
var modeHere = cm.getModeAt(pos)
if (!mode) mode = modeHere;
else if (mode != modeHere) return CodeMirror.Pass;
var insert = null;
if (mode.blockCommentStart && mode.blockCommentContinue) {
var line = cm.getLine(pos.line).slice(0, pos.ch)
var end = line.lastIndexOf(mode.blockCommentEnd), found
if (end != -1 && end == pos.ch - mode.blockCommentEnd.length) {
// Comment ended, don't continue it
} else if ((found = line.lastIndexOf(mode.blockCommentStart)) > -1 && found > end) {
insert = line.slice(0, found)
if (/\S/.test(insert)) {
insert = ""
for (var j = 0; j < found; ++j) insert += " "
}
} else if ((found = line.indexOf(mode.blockCommentContinue)) > -1 && !/\S/.test(line.slice(0, found))) {
insert = line.slice(0, found)
}
if (insert != null) insert += mode.blockCommentContinue
}
if (insert == null && mode.lineComment && continueLineCommentEnabled(cm)) {
var line = cm.getLine(pos.line), found = line.indexOf(mode.lineComment);
if (found > -1) {
insert = line.slice(0, found);
if (/\S/.test(insert)) insert = null;
else insert += mode.lineComment + line.slice(found + mode.lineComment.length).match(/^\s*/)[0];
}
}
if (insert == null) return CodeMirror.Pass;
inserts[i] = "\n" + insert;
}
cm.operation(function() {
for (var i = ranges.length - 1; i >= 0; i--)
cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+insert");
});
}
function continueLineCommentEnabled(cm) {
var opt = cm.getOption("continueComments");
if (opt && typeof opt == "object")
return opt.continueLineComment !== false;
return true;
}
CodeMirror.defineOption("continueComments", null, function(cm, val, prev) {
if (prev && prev != CodeMirror.Init)
cm.removeKeyMap("continueComment");
if (val) {
var key = "Enter";
if (typeof val == "string")
key = val;
else if (typeof val == "object" && val.key)
key = val.key;
var map = {name: "continueComment"};
map[key] = continueComment;
cm.addKeyMap(map);
}
});
});

View File

@ -1,32 +0,0 @@
.CodeMirror-dialog {
position: absolute;
left: 0; right: 0;
background: inherit;
z-index: 15;
padding: .1em .8em;
overflow: hidden;
color: inherit;
}
.CodeMirror-dialog-top {
border-bottom: 1px solid #eee;
top: 0;
}
.CodeMirror-dialog-bottom {
border-top: 1px solid #eee;
bottom: 0;
}
.CodeMirror-dialog input {
border: none;
outline: none;
background: transparent;
width: 20em;
color: inherit;
font-family: monospace;
}
.CodeMirror-dialog button {
font-size: 70%;
}

View File

@ -1,161 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
// Open simple dialogs on top of an editor. Relies on dialog.css.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
function dialogDiv(cm, template, bottom) {
var wrap = cm.getWrapperElement();
var dialog;
dialog = wrap.appendChild(document.createElement("div"));
if (bottom)
dialog.className = "CodeMirror-dialog CodeMirror-dialog-bottom";
else
dialog.className = "CodeMirror-dialog CodeMirror-dialog-top";
if (typeof template == "string") {
dialog.innerHTML = template;
} else { // Assuming it's a detached DOM element.
dialog.appendChild(template);
}
CodeMirror.addClass(wrap, 'dialog-opened');
return dialog;
}
function closeNotification(cm, newVal) {
if (cm.state.currentNotificationClose)
cm.state.currentNotificationClose();
cm.state.currentNotificationClose = newVal;
}
CodeMirror.defineExtension("openDialog", function(template, callback, options) {
if (!options) options = {};
closeNotification(this, null);
var dialog = dialogDiv(this, template, options.bottom);
var closed = false, me = this;
function close(newVal) {
if (typeof newVal == 'string') {
inp.value = newVal;
} else {
if (closed) return;
closed = true;
CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
dialog.parentNode.removeChild(dialog);
me.focus();
if (options.onClose) options.onClose(dialog);
}
}
var inp = dialog.getElementsByTagName("input")[0], button;
if (inp) {
inp.focus();
if (options.value) {
inp.value = options.value;
if (options.selectValueOnOpen !== false) {
inp.select();
}
}
if (options.onInput)
CodeMirror.on(inp, "input", function(e) { options.onInput(e, inp.value, close);});
if (options.onKeyUp)
CodeMirror.on(inp, "keyup", function(e) {options.onKeyUp(e, inp.value, close);});
CodeMirror.on(inp, "keydown", function(e) {
if (options && options.onKeyDown && options.onKeyDown(e, inp.value, close)) { return; }
if (e.keyCode == 27 || (options.closeOnEnter !== false && e.keyCode == 13)) {
inp.blur();
CodeMirror.e_stop(e);
close();
}
if (e.keyCode == 13) callback(inp.value, e);
});
if (options.closeOnBlur !== false) CodeMirror.on(inp, "blur", close);
} else if (button = dialog.getElementsByTagName("button")[0]) {
CodeMirror.on(button, "click", function() {
close();
me.focus();
});
if (options.closeOnBlur !== false) CodeMirror.on(button, "blur", close);
button.focus();
}
return close;
});
CodeMirror.defineExtension("openConfirm", function(template, callbacks, options) {
closeNotification(this, null);
var dialog = dialogDiv(this, template, options && options.bottom);
var buttons = dialog.getElementsByTagName("button");
var closed = false, me = this, blurring = 1;
function close() {
if (closed) return;
closed = true;
CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
dialog.parentNode.removeChild(dialog);
me.focus();
}
buttons[0].focus();
for (var i = 0; i < buttons.length; ++i) {
var b = buttons[i];
(function(callback) {
CodeMirror.on(b, "click", function(e) {
CodeMirror.e_preventDefault(e);
close();
if (callback) callback(me);
});
})(callbacks[i]);
CodeMirror.on(b, "blur", function() {
--blurring;
setTimeout(function() { if (blurring <= 0) close(); }, 200);
});
CodeMirror.on(b, "focus", function() { ++blurring; });
}
});
/*
* openNotification
* Opens a notification, that can be closed with an optional timer
* (default 5000ms timer) and always closes on click.
*
* If a notification is opened while another is opened, it will close the
* currently opened one and open the new one immediately.
*/
CodeMirror.defineExtension("openNotification", function(template, options) {
closeNotification(this, close);
var dialog = dialogDiv(this, template, options && options.bottom);
var closed = false, doneTimer;
var duration = options && typeof options.duration !== "undefined" ? options.duration : 5000;
function close() {
if (closed) return;
closed = true;
clearTimeout(doneTimer);
CodeMirror.rmClass(dialog.parentNode, 'dialog-opened');
dialog.parentNode.removeChild(dialog);
}
CodeMirror.on(dialog, 'click', function(e) {
CodeMirror.e_preventDefault(e);
close();
});
if (duration)
doneTimer = setTimeout(close, duration);
return close;
});
});

View File

@ -1,47 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"))
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod)
else // Plain browser env
mod(CodeMirror)
})(function(CodeMirror) {
"use strict"
CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
if (cm.state.autoRefresh) {
stopListening(cm, cm.state.autoRefresh)
cm.state.autoRefresh = null
}
if (val && cm.display.wrapper.offsetHeight == 0)
startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
})
function startListening(cm, state) {
function check() {
if (cm.display.wrapper.offsetHeight) {
stopListening(cm, state)
if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight)
cm.refresh()
} else {
state.timeout = setTimeout(check, state.delay)
}
}
state.timeout = setTimeout(check, state.delay)
state.hurry = function() {
clearTimeout(state.timeout)
state.timeout = setTimeout(check, 50)
}
CodeMirror.on(window, "mouseup", state.hurry)
CodeMirror.on(window, "keyup", state.hurry)
}
function stopListening(_cm, state) {
clearTimeout(state.timeout)
CodeMirror.off(window, "mouseup", state.hurry)
CodeMirror.off(window, "keyup", state.hurry)
}
});

View File

@ -1,6 +0,0 @@
.CodeMirror-fullscreen {
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
height: auto;
z-index: 9;
}

View File

@ -1,41 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("fullScreen", false, function(cm, val, old) {
if (old == CodeMirror.Init) old = false;
if (!old == !val) return;
if (val) setFullscreen(cm);
else setNormal(cm);
});
function setFullscreen(cm) {
var wrap = cm.getWrapperElement();
cm.state.fullScreenRestore = {scrollTop: window.pageYOffset, scrollLeft: window.pageXOffset,
width: wrap.style.width, height: wrap.style.height};
wrap.style.width = "";
wrap.style.height = "auto";
wrap.className += " CodeMirror-fullscreen";
document.documentElement.style.overflow = "hidden";
cm.refresh();
}
function setNormal(cm) {
var wrap = cm.getWrapperElement();
wrap.className = wrap.className.replace(/\s*CodeMirror-fullscreen\b/, "");
document.documentElement.style.overflow = "";
var info = cm.state.fullScreenRestore;
wrap.style.width = info.width; wrap.style.height = info.height;
window.scrollTo(info.scrollLeft, info.scrollTop);
cm.refresh();
}
});

View File

@ -1,127 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineExtension("addPanel", function(node, options) {
options = options || {};
if (!this.state.panels) initPanels(this);
var info = this.state.panels;
var wrapper = info.wrapper;
var cmWrapper = this.getWrapperElement();
var replace = options.replace instanceof Panel && !options.replace.cleared;
if (options.after instanceof Panel && !options.after.cleared) {
wrapper.insertBefore(node, options.before.node.nextSibling);
} else if (options.before instanceof Panel && !options.before.cleared) {
wrapper.insertBefore(node, options.before.node);
} else if (replace) {
wrapper.insertBefore(node, options.replace.node);
info.panels++;
options.replace.clear();
} else if (options.position == "bottom") {
wrapper.appendChild(node);
} else if (options.position == "before-bottom") {
wrapper.insertBefore(node, cmWrapper.nextSibling);
} else if (options.position == "after-top") {
wrapper.insertBefore(node, cmWrapper);
} else {
wrapper.insertBefore(node, wrapper.firstChild);
}
var height = (options && options.height) || node.offsetHeight;
this._setSize(null, info.heightLeft -= height);
if (!replace) {
info.panels++;
}
if (options.stable && isAtTop(this, node))
this.scrollTo(null, this.getScrollInfo().top + height)
return new Panel(this, node, options, height);
});
function Panel(cm, node, options, height) {
this.cm = cm;
this.node = node;
this.options = options;
this.height = height;
this.cleared = false;
}
Panel.prototype.clear = function() {
if (this.cleared) return;
this.cleared = true;
var info = this.cm.state.panels;
this.cm._setSize(null, info.heightLeft += this.height);
if (this.options.stable && isAtTop(this.cm, this.node))
this.cm.scrollTo(null, this.cm.getScrollInfo().top - this.height)
info.wrapper.removeChild(this.node);
if (--info.panels == 0) removePanels(this.cm);
};
Panel.prototype.changed = function(height) {
var newHeight = height == null ? this.node.offsetHeight : height;
var info = this.cm.state.panels;
this.cm._setSize(null, info.heightLeft -= (newHeight - this.height));
this.height = newHeight;
};
function initPanels(cm) {
var wrap = cm.getWrapperElement();
var style = window.getComputedStyle ? window.getComputedStyle(wrap) : wrap.currentStyle;
var height = parseInt(style.height);
var info = cm.state.panels = {
setHeight: wrap.style.height,
heightLeft: height,
panels: 0,
wrapper: document.createElement("div")
};
wrap.parentNode.insertBefore(info.wrapper, wrap);
var hasFocus = cm.hasFocus();
info.wrapper.appendChild(wrap);
if (hasFocus) cm.focus();
cm._setSize = cm.setSize;
if (height != null) cm.setSize = function(width, newHeight) {
if (newHeight == null) return this._setSize(width, newHeight);
info.setHeight = newHeight;
if (typeof newHeight != "number") {
var px = /^(\d+\.?\d*)px$/.exec(newHeight);
if (px) {
newHeight = Number(px[1]);
} else {
info.wrapper.style.height = newHeight;
newHeight = info.wrapper.offsetHeight;
info.wrapper.style.height = "";
}
}
cm._setSize(width, info.heightLeft += (newHeight - height));
height = newHeight;
};
}
function removePanels(cm) {
var info = cm.state.panels;
cm.state.panels = null;
var wrap = cm.getWrapperElement();
info.wrapper.parentNode.replaceChild(wrap, info.wrapper);
wrap.style.height = info.setHeight;
cm.setSize = cm._setSize;
cm.setSize();
}
function isAtTop(cm, dom) {
for (var sibling = dom.nextSibling; sibling; sibling = sibling.nextSibling)
if (sibling == cm.getWrapperElement()) return true
return false
}
});

View File

@ -1,63 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineOption("placeholder", "", function(cm, val, old) {
var prev = old && old != CodeMirror.Init;
if (val && !prev) {
cm.on("blur", onBlur);
cm.on("change", onChange);
cm.on("swapDoc", onChange);
onChange(cm);
} else if (!val && prev) {
cm.off("blur", onBlur);
cm.off("change", onChange);
cm.off("swapDoc", onChange);
clearPlaceholder(cm);
var wrapper = cm.getWrapperElement();
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "");
}
if (val && !cm.hasFocus()) onBlur(cm);
});
function clearPlaceholder(cm) {
if (cm.state.placeholder) {
cm.state.placeholder.parentNode.removeChild(cm.state.placeholder);
cm.state.placeholder = null;
}
}
function setPlaceholder(cm) {
clearPlaceholder(cm);
var elt = cm.state.placeholder = document.createElement("pre");
elt.style.cssText = "height: 0; overflow: visible";
elt.style.direction = cm.getOption("direction");
elt.className = "CodeMirror-placeholder CodeMirror-line-like";
var placeHolder = cm.getOption("placeholder")
if (typeof placeHolder == "string") placeHolder = document.createTextNode(placeHolder)
elt.appendChild(placeHolder)
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild);
}
function onBlur(cm) {
if (isEmpty(cm)) setPlaceholder(cm);
}
function onChange(cm) {
var wrapper = cm.getWrapperElement(), empty = isEmpty(cm);
wrapper.className = wrapper.className.replace(" CodeMirror-empty", "") + (empty ? " CodeMirror-empty" : "");
if (empty) setPlaceholder(cm);
else clearPlaceholder(cm);
}
function isEmpty(cm) {
return (cm.lineCount() === 1) && (cm.getLine(0) === "");
}
});

View File

@ -1,51 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("rulers", false, function(cm, val) {
if (cm.state.rulerDiv) {
cm.state.rulerDiv.parentElement.removeChild(cm.state.rulerDiv)
cm.state.rulerDiv = null
cm.off("refresh", drawRulers)
}
if (val && val.length) {
cm.state.rulerDiv = cm.display.lineSpace.parentElement.insertBefore(document.createElement("div"), cm.display.lineSpace)
cm.state.rulerDiv.className = "CodeMirror-rulers"
drawRulers(cm)
cm.on("refresh", drawRulers)
}
});
function drawRulers(cm) {
cm.state.rulerDiv.textContent = ""
var val = cm.getOption("rulers");
var cw = cm.defaultCharWidth();
var left = cm.charCoords(CodeMirror.Pos(cm.firstLine(), 0), "div").left;
cm.state.rulerDiv.style.minHeight = (cm.display.scroller.offsetHeight + 30) + "px";
for (var i = 0; i < val.length; i++) {
var elt = document.createElement("div");
elt.className = "CodeMirror-ruler";
var col, conf = val[i];
if (typeof conf == "number") {
col = conf;
} else {
col = conf.column;
if (conf.className) elt.className += " " + conf.className;
if (conf.color) elt.style.borderColor = conf.color;
if (conf.lineStyle) elt.style.borderLeftStyle = conf.lineStyle;
if (conf.width) elt.style.borderLeftWidth = conf.width;
}
elt.style.left = (left + col * cw) + "px";
cm.state.rulerDiv.appendChild(elt)
}
}
});

View File

@ -1,191 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var defaults = {
pairs: "()[]{}''\"\"",
closeBefore: ")]}'\":;>",
triples: "",
explode: "[]{}"
};
var Pos = CodeMirror.Pos;
CodeMirror.defineOption("autoCloseBrackets", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.removeKeyMap(keyMap);
cm.state.closeBrackets = null;
}
if (val) {
ensureBound(getOption(val, "pairs"))
cm.state.closeBrackets = val;
cm.addKeyMap(keyMap);
}
});
function getOption(conf, name) {
if (name == "pairs" && typeof conf == "string") return conf;
if (typeof conf == "object" && conf[name] != null) return conf[name];
return defaults[name];
}
var keyMap = {Backspace: handleBackspace, Enter: handleEnter};
function ensureBound(chars) {
for (var i = 0; i < chars.length; i++) {
var ch = chars.charAt(i), key = "'" + ch + "'"
if (!keyMap[key]) keyMap[key] = handler(ch)
}
}
ensureBound(defaults.pairs + "`")
function handler(ch) {
return function(cm) { return handleChar(cm, ch); };
}
function getConfig(cm) {
var deflt = cm.state.closeBrackets;
if (!deflt || deflt.override) return deflt;
var mode = cm.getModeAt(cm.getCursor());
return mode.closeBrackets || deflt;
}
function handleBackspace(cm) {
var conf = getConfig(cm);
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
var pairs = getOption(conf, "pairs");
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || pairs.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
for (var i = ranges.length - 1; i >= 0; i--) {
var cur = ranges[i].head;
cm.replaceRange("", Pos(cur.line, cur.ch - 1), Pos(cur.line, cur.ch + 1), "+delete");
}
}
function handleEnter(cm) {
var conf = getConfig(cm);
var explode = conf && getOption(conf, "explode");
if (!explode || cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var around = charsAround(cm, ranges[i].head);
if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
}
cm.operation(function() {
var linesep = cm.lineSeparator() || "\n";
cm.replaceSelection(linesep + linesep, null);
cm.execCommand("goCharLeft");
ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var line = ranges[i].head.line;
cm.indentLine(line, null, true);
cm.indentLine(line + 1, null, true);
}
});
}
function contractSelection(sel) {
var inverted = CodeMirror.cmpPos(sel.anchor, sel.head) > 0;
return {anchor: new Pos(sel.anchor.line, sel.anchor.ch + (inverted ? -1 : 1)),
head: new Pos(sel.head.line, sel.head.ch + (inverted ? 1 : -1))};
}
function handleChar(cm, ch) {
var conf = getConfig(cm);
if (!conf || cm.getOption("disableInput")) return CodeMirror.Pass;
var pairs = getOption(conf, "pairs");
var pos = pairs.indexOf(ch);
if (pos == -1) return CodeMirror.Pass;
var closeBefore = getOption(conf,"closeBefore");
var triples = getOption(conf, "triples");
var identical = pairs.charAt(pos + 1) == ch;
var ranges = cm.listSelections();
var opening = pos % 2 == 0;
var type;
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i], cur = range.head, curType;
var next = cm.getRange(cur, Pos(cur.line, cur.ch + 1));
if (opening && !range.empty()) {
curType = "surround";
} else if ((identical || !opening) && next == ch) {
if (identical && stringStartsAfter(cm, cur))
curType = "both";
else if (triples.indexOf(ch) >= 0 && cm.getRange(cur, Pos(cur.line, cur.ch + 3)) == ch + ch + ch)
curType = "skipThree";
else
curType = "skip";
} else if (identical && cur.ch > 1 && triples.indexOf(ch) >= 0 &&
cm.getRange(Pos(cur.line, cur.ch - 2), cur) == ch + ch) {
if (cur.ch > 2 && /\bstring/.test(cm.getTokenTypeAt(Pos(cur.line, cur.ch - 2)))) return CodeMirror.Pass;
curType = "addFour";
} else if (identical) {
var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
else return CodeMirror.Pass;
} else if (opening && (next.length === 0 || /\s/.test(next) || closeBefore.indexOf(next) > -1)) {
curType = "both";
} else {
return CodeMirror.Pass;
}
if (!type) type = curType;
else if (type != curType) return CodeMirror.Pass;
}
var left = pos % 2 ? pairs.charAt(pos - 1) : ch;
var right = pos % 2 ? ch : pairs.charAt(pos + 1);
cm.operation(function() {
if (type == "skip") {
cm.execCommand("goCharRight");
} else if (type == "skipThree") {
for (var i = 0; i < 3; i++)
cm.execCommand("goCharRight");
} else if (type == "surround") {
var sels = cm.getSelections();
for (var i = 0; i < sels.length; i++)
sels[i] = left + sels[i] + right;
cm.replaceSelections(sels, "around");
sels = cm.listSelections().slice();
for (var i = 0; i < sels.length; i++)
sels[i] = contractSelection(sels[i]);
cm.setSelections(sels);
} else if (type == "both") {
cm.replaceSelection(left + right, null);
cm.triggerElectric(left + right);
cm.execCommand("goCharLeft");
} else if (type == "addFour") {
cm.replaceSelection(left + left + left + left, "before");
cm.execCommand("goCharRight");
}
});
}
function charsAround(cm, pos) {
var str = cm.getRange(Pos(pos.line, pos.ch - 1),
Pos(pos.line, pos.ch + 1));
return str.length == 2 ? str : null;
}
function stringStartsAfter(cm, pos) {
var token = cm.getTokenAt(Pos(pos.line, pos.ch + 1))
return /\bstring/.test(token.type) && token.start == pos.ch &&
(pos.ch == 0 || !/\bstring/.test(cm.getTokenTypeAt(pos)))
}
});

View File

@ -1,184 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
/**
* Tag-closer extension for CodeMirror.
*
* This extension adds an "autoCloseTags" option that can be set to
* either true to get the default behavior, or an object to further
* configure its behavior.
*
* These are supported options:
*
* `whenClosing` (default true)
* Whether to autoclose when the '/' of a closing tag is typed.
* `whenOpening` (default true)
* Whether to autoclose the tag when the final '>' of an opening
* tag is typed.
* `dontCloseTags` (default is empty tags for HTML, none for XML)
* An array of tag names that should not be autoclosed.
* `indentTags` (default is block tags for HTML, none for XML)
* An array of tag names that should, when opened, cause a
* blank line to be added inside the tag, and the blank line and
* closing line to be indented.
* `emptyTags` (default is none)
* An array of XML tag names that should be autoclosed with '/>'.
*
* See demos/closetag.html for a usage example.
*/
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../fold/xml-fold"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineOption("autoCloseTags", false, function(cm, val, old) {
if (old != CodeMirror.Init && old)
cm.removeKeyMap("autoCloseTags");
if (!val) return;
var map = {name: "autoCloseTags"};
if (typeof val != "object" || val.whenClosing)
map["'/'"] = function(cm) { return autoCloseSlash(cm); };
if (typeof val != "object" || val.whenOpening)
map["'>'"] = function(cm) { return autoCloseGT(cm); };
cm.addKeyMap(map);
});
var htmlDontClose = ["area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param",
"source", "track", "wbr"];
var htmlIndent = ["applet", "blockquote", "body", "button", "div", "dl", "fieldset", "form", "frameset", "h1", "h2", "h3", "h4",
"h5", "h6", "head", "html", "iframe", "layer", "legend", "object", "ol", "p", "select", "table", "ul"];
function autoCloseGT(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
var opt = cm.getOption("autoCloseTags");
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
var tagInfo = inner.mode.xmlCurrentTag && inner.mode.xmlCurrentTag(state)
var tagName = tagInfo && tagInfo.name
if (!tagName) return CodeMirror.Pass
var html = inner.mode.configuration == "html";
var dontCloseTags = (typeof opt == "object" && opt.dontCloseTags) || (html && htmlDontClose);
var indentTags = (typeof opt == "object" && opt.indentTags) || (html && htmlIndent);
if (tok.end > pos.ch) tagName = tagName.slice(0, tagName.length - tok.end + pos.ch);
var lowerTagName = tagName.toLowerCase();
// Don't process the '>' at the end of an end-tag or self-closing tag
if (!tagName ||
tok.type == "string" && (tok.end != pos.ch || !/[\"\']/.test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1) ||
tok.type == "tag" && tagInfo.close ||
tok.string.indexOf("/") == (tok.string.length - 1) || // match something like <someTagName />
dontCloseTags && indexOf(dontCloseTags, lowerTagName) > -1 ||
closingTagExists(cm, inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state) || [], tagName, pos, true))
return CodeMirror.Pass;
var emptyTags = typeof opt == "object" && opt.emptyTags;
if (emptyTags && indexOf(emptyTags, tagName) > -1) {
replacements[i] = { text: "/>", newPos: CodeMirror.Pos(pos.line, pos.ch + 2) };
continue;
}
var indent = indentTags && indexOf(indentTags, lowerTagName) > -1;
replacements[i] = {indent: indent,
text: ">" + (indent ? "\n\n" : "") + "</" + tagName + ">",
newPos: indent ? CodeMirror.Pos(pos.line + 1, 0) : CodeMirror.Pos(pos.line, pos.ch + 1)};
}
var dontIndentOnAutoClose = (typeof opt == "object" && opt.dontIndentOnAutoClose);
for (var i = ranges.length - 1; i >= 0; i--) {
var info = replacements[i];
cm.replaceRange(info.text, ranges[i].head, ranges[i].anchor, "+insert");
var sel = cm.listSelections().slice(0);
sel[i] = {head: info.newPos, anchor: info.newPos};
cm.setSelections(sel);
if (!dontIndentOnAutoClose && info.indent) {
cm.indentLine(info.newPos.line, null, true);
cm.indentLine(info.newPos.line + 1, null, true);
}
}
}
function autoCloseCurrent(cm, typingSlash) {
var ranges = cm.listSelections(), replacements = [];
var head = typingSlash ? "/" : "</";
var opt = cm.getOption("autoCloseTags");
var dontIndentOnAutoClose = (typeof opt == "object" && opt.dontIndentOnSlash);
for (var i = 0; i < ranges.length; i++) {
if (!ranges[i].empty()) return CodeMirror.Pass;
var pos = ranges[i].head, tok = cm.getTokenAt(pos);
var inner = CodeMirror.innerMode(cm.getMode(), tok.state), state = inner.state;
if (typingSlash && (tok.type == "string" || tok.string.charAt(0) != "<" ||
tok.start != pos.ch - 1))
return CodeMirror.Pass;
// Kludge to get around the fact that we are not in XML mode
// when completing in JS/CSS snippet in htmlmixed mode. Does not
// work for other XML embedded languages (there is no general
// way to go from a mixed mode to its current XML state).
var replacement, mixed = inner.mode.name != "xml" && cm.getMode().name == "htmlmixed"
if (mixed && inner.mode.name == "javascript") {
replacement = head + "script";
} else if (mixed && inner.mode.name == "css") {
replacement = head + "style";
} else {
var context = inner.mode.xmlCurrentContext && inner.mode.xmlCurrentContext(state)
if (!context || (context.length && closingTagExists(cm, context, context[context.length - 1], pos)))
return CodeMirror.Pass;
replacement = head + context[context.length - 1]
}
if (cm.getLine(pos.line).charAt(tok.end) != ">") replacement += ">";
replacements[i] = replacement;
}
cm.replaceSelections(replacements);
ranges = cm.listSelections();
if (!dontIndentOnAutoClose) {
for (var i = 0; i < ranges.length; i++)
if (i == ranges.length - 1 || ranges[i].head.line < ranges[i + 1].head.line)
cm.indentLine(ranges[i].head.line);
}
}
function autoCloseSlash(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
return autoCloseCurrent(cm, true);
}
CodeMirror.commands.closeTag = function(cm) { return autoCloseCurrent(cm); };
function indexOf(collection, elt) {
if (collection.indexOf) return collection.indexOf(elt);
for (var i = 0, e = collection.length; i < e; ++i)
if (collection[i] == elt) return i;
return -1;
}
// If xml-fold is loaded, we use its functionality to try and verify
// whether a given tag is actually unclosed.
function closingTagExists(cm, context, tagName, pos, newTag) {
if (!CodeMirror.scanForClosingTag) return false;
var end = Math.min(cm.lastLine() + 1, pos.line + 500);
var nextClose = CodeMirror.scanForClosingTag(cm, pos, null, end);
if (!nextClose || nextClose.tag != tagName) return false;
// If the immediate wrapping context contains onCx instances of
// the same tag, a closing tag only exists if there are at least
// that many closing tags of that type following.
var onCx = newTag ? 1 : 0
for (var i = context.length - 1; i >= 0; i--) {
if (context[i] == tagName) ++onCx
else break
}
pos = nextClose.to;
for (var i = 1; i < onCx; i++) {
var next = CodeMirror.scanForClosingTag(cm, pos, null, end);
if (!next || next.tag != tagName) return false;
pos = next.to;
}
return true;
}
});

View File

@ -1,99 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var listRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]\s|[*+-]\s|(\d+)([.)]))(\s*)/,
emptyListRE = /^(\s*)(>[> ]*|[*+-] \[[x ]\]|[*+-]|(\d+)[.)])(\s*)$/,
unorderedListRE = /[*+-]\s/;
CodeMirror.commands.newlineAndIndentContinueMarkdownList = function(cm) {
if (cm.getOption("disableInput")) return CodeMirror.Pass;
var ranges = cm.listSelections(), replacements = [];
for (var i = 0; i < ranges.length; i++) {
var pos = ranges[i].head;
// If we're not in Markdown mode, fall back to normal newlineAndIndent
var eolState = cm.getStateAfter(pos.line);
var inner = CodeMirror.innerMode(cm.getMode(), eolState);
if (inner.mode.name !== "markdown") {
cm.execCommand("newlineAndIndent");
return;
} else {
eolState = inner.state;
}
var inList = eolState.list !== false;
var inQuote = eolState.quote !== 0;
var line = cm.getLine(pos.line), match = listRE.exec(line);
var cursorBeforeBullet = /^\s*$/.test(line.slice(0, pos.ch));
if (!ranges[i].empty() || (!inList && !inQuote) || !match || cursorBeforeBullet) {
cm.execCommand("newlineAndIndent");
return;
}
if (emptyListRE.test(line)) {
if (!/>\s*$/.test(line)) cm.replaceRange("", {
line: pos.line, ch: 0
}, {
line: pos.line, ch: pos.ch + 1
});
replacements[i] = "\n";
} else {
var indent = match[1], after = match[5];
var numbered = !(unorderedListRE.test(match[2]) || match[2].indexOf(">") >= 0);
var bullet = numbered ? (parseInt(match[3], 10) + 1) + match[4] : match[2].replace("x", " ");
replacements[i] = "\n" + indent + bullet + after;
if (numbered) incrementRemainingMarkdownListNumbers(cm, pos);
}
}
cm.replaceSelections(replacements);
};
// Auto-updating Markdown list numbers when a new item is added to the
// middle of a list
function incrementRemainingMarkdownListNumbers(cm, pos) {
var startLine = pos.line, lookAhead = 0, skipCount = 0;
var startItem = listRE.exec(cm.getLine(startLine)), startIndent = startItem[1];
do {
lookAhead += 1;
var nextLineNumber = startLine + lookAhead;
var nextLine = cm.getLine(nextLineNumber), nextItem = listRE.exec(nextLine);
if (nextItem) {
var nextIndent = nextItem[1];
var newNumber = (parseInt(startItem[3], 10) + lookAhead - skipCount);
var nextNumber = (parseInt(nextItem[3], 10)), itemNumber = nextNumber;
if (startIndent === nextIndent && !isNaN(nextNumber)) {
if (newNumber === nextNumber) itemNumber = nextNumber + 1;
if (newNumber > nextNumber) itemNumber = newNumber + 1;
cm.replaceRange(
nextLine.replace(listRE, nextIndent + itemNumber + nextItem[4] + nextItem[5]),
{
line: nextLineNumber, ch: 0
}, {
line: nextLineNumber, ch: nextLine.length
});
} else {
if (startIndent.length > nextIndent.length) return;
// This doesn't run if the next line immediatley indents, as it is
// not clear of the users intention (new indented item or same level)
if ((startIndent.length < nextIndent.length) && (lookAhead === 1)) return;
skipCount += 1;
}
}
} while (nextItem);
}
});

View File

@ -1,150 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
var ie_lt8 = /MSIE \d/.test(navigator.userAgent) &&
(document.documentMode == null || document.documentMode < 8);
var Pos = CodeMirror.Pos;
var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<", "<": ">>", ">": "<<"};
function bracketRegex(config) {
return config && config.bracketRegex || /[(){}[\]]/
}
function findMatchingBracket(cm, where, config) {
var line = cm.getLineHandle(where.line), pos = where.ch - 1;
var afterCursor = config && config.afterCursor
if (afterCursor == null)
afterCursor = /(^| )cm-fat-cursor($| )/.test(cm.getWrapperElement().className)
var re = bracketRegex(config)
// A cursor is defined as between two characters, but in in vim command mode
// (i.e. not insert mode), the cursor is visually represented as a
// highlighted box on top of the 2nd character. Otherwise, we allow matches
// from before or after the cursor.
var match = (!afterCursor && pos >= 0 && re.test(line.text.charAt(pos)) && matching[line.text.charAt(pos)]) ||
re.test(line.text.charAt(pos + 1)) && matching[line.text.charAt(++pos)];
if (!match) return null;
var dir = match.charAt(1) == ">" ? 1 : -1;
if (config && config.strict && (dir > 0) != (pos == where.ch)) return null;
var style = cm.getTokenTypeAt(Pos(where.line, pos + 1));
var found = scanForBracket(cm, Pos(where.line, pos + (dir > 0 ? 1 : 0)), dir, style || null, config);
if (found == null) return null;
return {from: Pos(where.line, pos), to: found && found.pos,
match: found && found.ch == match.charAt(0), forward: dir > 0};
}
// bracketRegex is used to specify which type of bracket to scan
// should be a regexp, e.g. /[[\]]/
//
// Note: If "where" is on an open bracket, then this bracket is ignored.
//
// Returns false when no bracket was found, null when it reached
// maxScanLines and gave up
function scanForBracket(cm, where, dir, style, config) {
var maxScanLen = (config && config.maxScanLineLength) || 10000;
var maxScanLines = (config && config.maxScanLines) || 1000;
var stack = [];
var re = bracketRegex(config)
var lineEnd = dir > 0 ? Math.min(where.line + maxScanLines, cm.lastLine() + 1)
: Math.max(cm.firstLine() - 1, where.line - maxScanLines);
for (var lineNo = where.line; lineNo != lineEnd; lineNo += dir) {
var line = cm.getLine(lineNo);
if (!line) continue;
var pos = dir > 0 ? 0 : line.length - 1, end = dir > 0 ? line.length : -1;
if (line.length > maxScanLen) continue;
if (lineNo == where.line) pos = where.ch - (dir < 0 ? 1 : 0);
for (; pos != end; pos += dir) {
var ch = line.charAt(pos);
if (re.test(ch) && (style === undefined || cm.getTokenTypeAt(Pos(lineNo, pos + 1)) == style)) {
var match = matching[ch];
if (match && (match.charAt(1) == ">") == (dir > 0)) stack.push(ch);
else if (!stack.length) return {pos: Pos(lineNo, pos), ch: ch};
else stack.pop();
}
}
}
return lineNo - dir == (dir > 0 ? cm.lastLine() : cm.firstLine()) ? false : null;
}
function matchBrackets(cm, autoclear, config) {
// Disable brace matching in long lines, since it'll cause hugely slow updates
var maxHighlightLen = cm.state.matchBrackets.maxHighlightLineLength || 1000;
var marks = [], ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++) {
var match = ranges[i].empty() && findMatchingBracket(cm, ranges[i].head, config);
if (match && cm.getLine(match.from.line).length <= maxHighlightLen) {
var style = match.match ? "CodeMirror-matchingbracket" : "CodeMirror-nonmatchingbracket";
marks.push(cm.markText(match.from, Pos(match.from.line, match.from.ch + 1), {className: style}));
if (match.to && cm.getLine(match.to.line).length <= maxHighlightLen)
marks.push(cm.markText(match.to, Pos(match.to.line, match.to.ch + 1), {className: style}));
}
}
if (marks.length) {
// Kludge to work around the IE bug from issue #1193, where text
// input stops going to the textare whever this fires.
if (ie_lt8 && cm.state.focused) cm.focus();
var clear = function() {
cm.operation(function() {
for (var i = 0; i < marks.length; i++) marks[i].clear();
});
};
if (autoclear) setTimeout(clear, 800);
else return clear;
}
}
function doMatchBrackets(cm) {
cm.operation(function() {
if (cm.state.matchBrackets.currentlyHighlighted) {
cm.state.matchBrackets.currentlyHighlighted();
cm.state.matchBrackets.currentlyHighlighted = null;
}
cm.state.matchBrackets.currentlyHighlighted = matchBrackets(cm, false, cm.state.matchBrackets);
});
}
CodeMirror.defineOption("matchBrackets", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.off("cursorActivity", doMatchBrackets);
if (cm.state.matchBrackets && cm.state.matchBrackets.currentlyHighlighted) {
cm.state.matchBrackets.currentlyHighlighted();
cm.state.matchBrackets.currentlyHighlighted = null;
}
}
if (val) {
cm.state.matchBrackets = typeof val == "object" ? val : {};
cm.on("cursorActivity", doMatchBrackets);
}
});
CodeMirror.defineExtension("matchBrackets", function() {matchBrackets(this, true);});
CodeMirror.defineExtension("findMatchingBracket", function(pos, config, oldConfig){
// Backwards-compatibility kludge
if (oldConfig || typeof config == "boolean") {
if (!oldConfig) {
config = config ? {strict: true} : null
} else {
oldConfig.strict = config
config = oldConfig
}
}
return findMatchingBracket(this, pos, config)
});
CodeMirror.defineExtension("scanForBracket", function(pos, dir, style, config){
return scanForBracket(this, pos, dir, style, config);
});
});

View File

@ -1,66 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("../fold/xml-fold"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "../fold/xml-fold"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("matchTags", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.off("cursorActivity", doMatchTags);
cm.off("viewportChange", maybeUpdateMatch);
clear(cm);
}
if (val) {
cm.state.matchBothTags = typeof val == "object" && val.bothTags;
cm.on("cursorActivity", doMatchTags);
cm.on("viewportChange", maybeUpdateMatch);
doMatchTags(cm);
}
});
function clear(cm) {
if (cm.state.tagHit) cm.state.tagHit.clear();
if (cm.state.tagOther) cm.state.tagOther.clear();
cm.state.tagHit = cm.state.tagOther = null;
}
function doMatchTags(cm) {
cm.state.failedTagMatch = false;
cm.operation(function() {
clear(cm);
if (cm.somethingSelected()) return;
var cur = cm.getCursor(), range = cm.getViewport();
range.from = Math.min(range.from, cur.line); range.to = Math.max(cur.line + 1, range.to);
var match = CodeMirror.findMatchingTag(cm, cur, range);
if (!match) return;
if (cm.state.matchBothTags) {
var hit = match.at == "open" ? match.open : match.close;
if (hit) cm.state.tagHit = cm.markText(hit.from, hit.to, {className: "CodeMirror-matchingtag"});
}
var other = match.at == "close" ? match.open : match.close;
if (other)
cm.state.tagOther = cm.markText(other.from, other.to, {className: "CodeMirror-matchingtag"});
else
cm.state.failedTagMatch = true;
});
}
function maybeUpdateMatch(cm) {
if (cm.state.failedTagMatch) doMatchTags(cm);
}
CodeMirror.commands.toMatchingTag = function(cm) {
var found = CodeMirror.findMatchingTag(cm, cm.getCursor());
if (found) {
var other = found.at == "close" ? found.open : found.close;
if (other) cm.extendSelection(other.to, other.from);
}
};
});

View File

@ -1,27 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
CodeMirror.defineOption("showTrailingSpace", false, function(cm, val, prev) {
if (prev == CodeMirror.Init) prev = false;
if (prev && !val)
cm.removeOverlay("trailingspace");
else if (!prev && val)
cm.addOverlay({
token: function(stream) {
for (var l = stream.string.length, i = l; i && /\s/.test(stream.string.charAt(i - 1)); --i) {}
if (i > stream.pos) { stream.pos = i; return null; }
stream.pos = l;
return "trailingspace";
},
name: "trailingspace"
});
});
});

View File

@ -1,105 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("fold", "brace", function(cm, start) {
var line = start.line, lineText = cm.getLine(line);
var tokenType;
function findOpening(openCh) {
for (var at = start.ch, pass = 0;;) {
var found = at <= 0 ? -1 : lineText.lastIndexOf(openCh, at - 1);
if (found == -1) {
if (pass == 1) break;
pass = 1;
at = lineText.length;
continue;
}
if (pass == 1 && found < start.ch) break;
tokenType = cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1));
if (!/^(comment|string)/.test(tokenType)) return found + 1;
at = found - 1;
}
}
var startToken = "{", endToken = "}", startCh = findOpening("{");
if (startCh == null) {
startToken = "[", endToken = "]";
startCh = findOpening("[");
}
if (startCh == null) return;
var count = 1, lastLine = cm.lastLine(), end, endCh;
outer: for (var i = line; i <= lastLine; ++i) {
var text = cm.getLine(i), pos = i == line ? startCh : 0;
for (;;) {
var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
if (nextOpen < 0) nextOpen = text.length;
if (nextClose < 0) nextClose = text.length;
pos = Math.min(nextOpen, nextClose);
if (pos == text.length) break;
if (cm.getTokenTypeAt(CodeMirror.Pos(i, pos + 1)) == tokenType) {
if (pos == nextOpen) ++count;
else if (!--count) { end = i; endCh = pos; break outer; }
}
++pos;
}
}
if (end == null || line == end) return;
return {from: CodeMirror.Pos(line, startCh),
to: CodeMirror.Pos(end, endCh)};
});
CodeMirror.registerHelper("fold", "import", function(cm, start) {
function hasImport(line) {
if (line < cm.firstLine() || line > cm.lastLine()) return null;
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
if (start.type != "keyword" || start.string != "import") return null;
// Now find closing semicolon, return its position
for (var i = line, e = Math.min(cm.lastLine(), line + 10); i <= e; ++i) {
var text = cm.getLine(i), semi = text.indexOf(";");
if (semi != -1) return {startCh: start.end, end: CodeMirror.Pos(i, semi)};
}
}
var startLine = start.line, has = hasImport(startLine), prev;
if (!has || hasImport(startLine - 1) || ((prev = hasImport(startLine - 2)) && prev.end.line == startLine - 1))
return null;
for (var end = has.end;;) {
var next = hasImport(end.line + 1);
if (next == null) break;
end = next.end;
}
return {from: cm.clipPos(CodeMirror.Pos(startLine, has.startCh + 1)), to: end};
});
CodeMirror.registerHelper("fold", "include", function(cm, start) {
function hasInclude(line) {
if (line < cm.firstLine() || line > cm.lastLine()) return null;
var start = cm.getTokenAt(CodeMirror.Pos(line, 1));
if (!/\S/.test(start.string)) start = cm.getTokenAt(CodeMirror.Pos(line, start.end + 1));
if (start.type == "meta" && start.string.slice(0, 8) == "#include") return start.start + 8;
}
var startLine = start.line, has = hasInclude(startLine);
if (has == null || hasInclude(startLine - 1) != null) return null;
for (var end = startLine;;) {
var next = hasInclude(end + 1);
if (next == null) break;
++end;
}
return {from: CodeMirror.Pos(startLine, has + 1),
to: cm.clipPos(CodeMirror.Pos(end))};
});
});

View File

@ -1,59 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerGlobalHelper("fold", "comment", function(mode) {
return mode.blockCommentStart && mode.blockCommentEnd;
}, function(cm, start) {
var mode = cm.getModeAt(start), startToken = mode.blockCommentStart, endToken = mode.blockCommentEnd;
if (!startToken || !endToken) return;
var line = start.line, lineText = cm.getLine(line);
var startCh;
for (var at = start.ch, pass = 0;;) {
var found = at <= 0 ? -1 : lineText.lastIndexOf(startToken, at - 1);
if (found == -1) {
if (pass == 1) return;
pass = 1;
at = lineText.length;
continue;
}
if (pass == 1 && found < start.ch) return;
if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
(found == 0 || lineText.slice(found - endToken.length, found) == endToken ||
!/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
startCh = found + startToken.length;
break;
}
at = found - 1;
}
var depth = 1, lastLine = cm.lastLine(), end, endCh;
outer: for (var i = line; i <= lastLine; ++i) {
var text = cm.getLine(i), pos = i == line ? startCh : 0;
for (;;) {
var nextOpen = text.indexOf(startToken, pos), nextClose = text.indexOf(endToken, pos);
if (nextOpen < 0) nextOpen = text.length;
if (nextClose < 0) nextClose = text.length;
pos = Math.min(nextOpen, nextClose);
if (pos == text.length) break;
if (pos == nextOpen) ++depth;
else if (!--depth) { end = i; endCh = pos; break outer; }
++pos;
}
}
if (end == null || line == end && endCh == startCh) return;
return {from: CodeMirror.Pos(line, startCh),
to: CodeMirror.Pos(end, endCh)};
});
});

View File

@ -1,152 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function doFold(cm, pos, options, force) {
if (options && options.call) {
var finder = options;
options = null;
} else {
var finder = getOption(cm, options, "rangeFinder");
}
if (typeof pos == "number") pos = CodeMirror.Pos(pos, 0);
var minSize = getOption(cm, options, "minFoldSize");
function getRange(allowFolded) {
var range = finder(cm, pos);
if (!range || range.to.line - range.from.line < minSize) return null;
var marks = cm.findMarksAt(range.from);
for (var i = 0; i < marks.length; ++i) {
if (marks[i].__isFold && force !== "fold") {
if (!allowFolded) return null;
range.cleared = true;
marks[i].clear();
}
}
return range;
}
var range = getRange(true);
if (getOption(cm, options, "scanUp")) while (!range && pos.line > cm.firstLine()) {
pos = CodeMirror.Pos(pos.line - 1, 0);
range = getRange(false);
}
if (!range || range.cleared || force === "unfold") return;
var myWidget = makeWidget(cm, options);
CodeMirror.on(myWidget, "mousedown", function(e) {
myRange.clear();
CodeMirror.e_preventDefault(e);
});
var myRange = cm.markText(range.from, range.to, {
replacedWith: myWidget,
clearOnEnter: getOption(cm, options, "clearOnEnter"),
__isFold: true
});
myRange.on("clear", function(from, to) {
CodeMirror.signal(cm, "unfold", cm, from, to);
});
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
}
function makeWidget(cm, options) {
var widget = getOption(cm, options, "widget");
if (typeof widget == "string") {
var text = document.createTextNode(widget);
widget = document.createElement("span");
widget.appendChild(text);
widget.className = "CodeMirror-foldmarker";
} else if (widget) {
widget = widget.cloneNode(true)
}
return widget;
}
// Clumsy backwards-compatible interface
CodeMirror.newFoldFunction = function(rangeFinder, widget) {
return function(cm, pos) { doFold(cm, pos, {rangeFinder: rangeFinder, widget: widget}); };
};
// New-style interface
CodeMirror.defineExtension("foldCode", function(pos, options, force) {
doFold(this, pos, options, force);
});
CodeMirror.defineExtension("isFolded", function(pos) {
var marks = this.findMarksAt(pos);
for (var i = 0; i < marks.length; ++i)
if (marks[i].__isFold) return true;
});
CodeMirror.commands.toggleFold = function(cm) {
cm.foldCode(cm.getCursor());
};
CodeMirror.commands.fold = function(cm) {
cm.foldCode(cm.getCursor(), null, "fold");
};
CodeMirror.commands.unfold = function(cm) {
cm.foldCode(cm.getCursor(), null, "unfold");
};
CodeMirror.commands.foldAll = function(cm) {
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
cm.foldCode(CodeMirror.Pos(i, 0), null, "fold");
});
};
CodeMirror.commands.unfoldAll = function(cm) {
cm.operation(function() {
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++)
cm.foldCode(CodeMirror.Pos(i, 0), null, "unfold");
});
};
CodeMirror.registerHelper("fold", "combine", function() {
var funcs = Array.prototype.slice.call(arguments, 0);
return function(cm, start) {
for (var i = 0; i < funcs.length; ++i) {
var found = funcs[i](cm, start);
if (found) return found;
}
};
});
CodeMirror.registerHelper("fold", "auto", function(cm, start) {
var helpers = cm.getHelpers(start, "fold");
for (var i = 0; i < helpers.length; i++) {
var cur = helpers[i](cm, start);
if (cur) return cur;
}
});
var defaultOptions = {
rangeFinder: CodeMirror.fold.auto,
widget: "\u2194",
minFoldSize: 0,
scanUp: false,
clearOnEnter: true
};
CodeMirror.defineOption("foldOptions", null);
function getOption(cm, options, name) {
if (options && options[name] !== undefined)
return options[name];
var editorOptions = cm.options.foldOptions;
if (editorOptions && editorOptions[name] !== undefined)
return editorOptions[name];
return defaultOptions[name];
}
CodeMirror.defineExtension("foldOption", function(options, name) {
return getOption(this, options, name);
});
});

View File

@ -1,20 +0,0 @@
.CodeMirror-foldmarker {
color: blue;
text-shadow: #b9f 1px 1px 2px, #b9f -1px -1px 2px, #b9f 1px -1px 2px, #b9f -1px 1px 2px;
font-family: arial;
line-height: .3;
cursor: pointer;
}
.CodeMirror-foldgutter {
width: .7em;
}
.CodeMirror-foldgutter-open,
.CodeMirror-foldgutter-folded {
cursor: pointer;
}
.CodeMirror-foldgutter-open:after {
content: "\25BE";
}
.CodeMirror-foldgutter-folded:after {
content: "\25B8";
}

View File

@ -1,151 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"), require("./foldcode"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror", "./foldcode"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("foldGutter", false, function(cm, val, old) {
if (old && old != CodeMirror.Init) {
cm.clearGutter(cm.state.foldGutter.options.gutter);
cm.state.foldGutter = null;
cm.off("gutterClick", onGutterClick);
cm.off("changes", onChange);
cm.off("viewportChange", onViewportChange);
cm.off("fold", onFold);
cm.off("unfold", onFold);
cm.off("swapDoc", onChange);
}
if (val) {
cm.state.foldGutter = new State(parseOptions(val));
updateInViewport(cm);
cm.on("gutterClick", onGutterClick);
cm.on("changes", onChange);
cm.on("viewportChange", onViewportChange);
cm.on("fold", onFold);
cm.on("unfold", onFold);
cm.on("swapDoc", onChange);
}
});
var Pos = CodeMirror.Pos;
function State(options) {
this.options = options;
this.from = this.to = 0;
}
function parseOptions(opts) {
if (opts === true) opts = {};
if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
return opts;
}
function isFolded(cm, line) {
var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
for (var i = 0; i < marks.length; ++i) {
if (marks[i].__isFold) {
var fromPos = marks[i].find(-1);
if (fromPos && fromPos.line === line)
return marks[i];
}
}
}
function marker(spec) {
if (typeof spec == "string") {
var elt = document.createElement("div");
elt.className = spec + " CodeMirror-guttermarker-subtle";
return elt;
} else {
return spec.cloneNode(true);
}
}
function updateFoldInfo(cm, from, to) {
var opts = cm.state.foldGutter.options, cur = from;
var minSize = cm.foldOption(opts, "minFoldSize");
var func = cm.foldOption(opts, "rangeFinder");
cm.eachLine(from, to, function(line) {
var mark = null;
if (isFolded(cm, cur)) {
mark = marker(opts.indicatorFolded);
} else {
var pos = Pos(cur, 0);
var range = func && func(cm, pos);
if (range && range.to.line - range.from.line >= minSize)
mark = marker(opts.indicatorOpen);
}
cm.setGutterMarker(line, opts.gutter, mark);
++cur;
});
}
function updateInViewport(cm) {
var vp = cm.getViewport(), state = cm.state.foldGutter;
if (!state) return;
cm.operation(function() {
updateFoldInfo(cm, vp.from, vp.to);
});
state.from = vp.from; state.to = vp.to;
}
function onGutterClick(cm, line, gutter) {
var state = cm.state.foldGutter;
if (!state) return;
var opts = state.options;
if (gutter != opts.gutter) return;
var folded = isFolded(cm, line);
if (folded) folded.clear();
else cm.foldCode(Pos(line, 0), opts);
}
function onChange(cm) {
var state = cm.state.foldGutter;
if (!state) return;
var opts = state.options;
state.from = state.to = 0;
clearTimeout(state.changeUpdate);
state.changeUpdate = setTimeout(function() { updateInViewport(cm); }, opts.foldOnChangeTimeSpan || 600);
}
function onViewportChange(cm) {
var state = cm.state.foldGutter;
if (!state) return;
var opts = state.options;
clearTimeout(state.changeUpdate);
state.changeUpdate = setTimeout(function() {
var vp = cm.getViewport();
if (state.from == state.to || vp.from - state.to > 20 || state.from - vp.to > 20) {
updateInViewport(cm);
} else {
cm.operation(function() {
if (vp.from < state.from) {
updateFoldInfo(cm, vp.from, state.from);
state.from = vp.from;
}
if (vp.to > state.to) {
updateFoldInfo(cm, state.to, vp.to);
state.to = vp.to;
}
});
}
}, opts.updateViewportTimeSpan || 400);
}
function onFold(cm, from) {
var state = cm.state.foldGutter;
if (!state) return;
var line = from.line;
if (line >= state.from && line < state.to)
updateFoldInfo(cm, line, line + 1);
}
});

View File

@ -1,48 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
function lineIndent(cm, lineNo) {
var text = cm.getLine(lineNo)
var spaceTo = text.search(/\S/)
if (spaceTo == -1 || /\bcomment\b/.test(cm.getTokenTypeAt(CodeMirror.Pos(lineNo, spaceTo + 1))))
return -1
return CodeMirror.countColumn(text, null, cm.getOption("tabSize"))
}
CodeMirror.registerHelper("fold", "indent", function(cm, start) {
var myIndent = lineIndent(cm, start.line)
if (myIndent < 0) return
var lastLineInFold = null
// Go through lines until we find a line that definitely doesn't belong in
// the block we're folding, or to the end.
for (var i = start.line + 1, end = cm.lastLine(); i <= end; ++i) {
var indent = lineIndent(cm, i)
if (indent == -1) {
} else if (indent > myIndent) {
// Lines with a greater indent are considered part of the block.
lastLineInFold = i;
} else {
// If this line has non-space, non-comment content, and is
// indented less or equal to the start line, it is the start of
// another block.
break;
}
}
if (lastLineInFold) return {
from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
to: CodeMirror.Pos(lastLineInFold, cm.getLine(lastLineInFold).length)
};
});
});

View File

@ -1,49 +0,0 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.registerHelper("fold", "markdown", function(cm, start) {
var maxDepth = 100;
function isHeader(lineNo) {
var tokentype = cm.getTokenTypeAt(CodeMirror.Pos(lineNo, 0));
return tokentype && /\bheader\b/.test(tokentype);
}
function headerLevel(lineNo, line, nextLine) {
var match = line && line.match(/^#+/);
if (match && isHeader(lineNo)) return match[0].length;
match = nextLine && nextLine.match(/^[=\-]+\s*$/);
if (match && isHeader(lineNo + 1)) return nextLine[0] == "=" ? 1 : 2;
return maxDepth;
}
var firstLine = cm.getLine(start.line), nextLine = cm.getLine(start.line + 1);
var level = headerLevel(start.line, firstLine, nextLine);
if (level === maxDepth) return undefined;
var lastLineNo = cm.lastLine();
var end = start.line, nextNextLine = cm.getLine(end + 2);
while (end < lastLineNo) {
if (headerLevel(end + 1, nextLine, nextNextLine) <= level) break;
++end;
nextLine = nextNextLine;
nextNextLine = cm.getLine(end + 2);
}
return {
from: CodeMirror.Pos(start.line, firstLine.length),
to: CodeMirror.Pos(end, cm.getLine(end).length)
};
});
});

Some files were not shown because too many files have changed in this diff Show More