Compare commits

...

237 Commits
v1.13 ... v1.x

Author SHA1 Message Date
Adarnof
26eebec918 Version bump to v1.15.8 2018-04-15 19:15:11 -04:00
Adarnof
072d1b9db6 Sanitize username on Discord user join.
Thanks @iakopo
2018-04-02 20:41:34 -04:00
Adarnof
8c957e9cb7 Correct queryset ordering.
Closes #1001
2018-03-27 15:52:32 -04:00
Adarnof
69a686a98a Group list API endpoint has moved.
Allow infinite group cache age.

Thanks @TargetZ3R0

(cherry-picked from bdb3ab366f)
2018-03-22 18:05:45 -04:00
Adarnof
c69b41738b Stop using the patch method for setting roles.
Switch to dedicated add/remove endpoints.
Allow setting max cache age to None for infinite.

Apparently patch has issues.

Thanks @TargetZ3R0 and Discord devs <3
2018-03-19 18:17:29 -04:00
Adarnof
a096023553 Prevent checking out v2 when trying to install v1 2018-02-24 01:30:07 -05:00
Adarnof
5eecee49f5 Correct broken template tags.
(cherry picked from commit 77c93ed96b)
2018-02-23 22:28:19 -05:00
Ariel Rin
d8f4d56dd8 Add Timerboard Structures, step 2 (#976)
Added additional labels for added structure types
2018-02-23 21:35:49 -05:00
Adarnof
d58ac8a718 Remove references to removed setting.
Version bump.
2018-02-23 13:57:39 -05:00
Adarnof
d503243e12 Use new endpoint for adding Discord users.
Closes #974
(cherry picked from commit 70c2a4a6e4)
2018-02-23 13:36:12 -05:00
Adarnof
5962f0f29f Do not sanitize Discord names
(cherry-picked from commit 8ce8789631)
2018-02-23 13:36:12 -05:00
Adarnof
a2f4226381 Delete Discord users if they've left the server.
Closes #968

(cherry picked from commit 99b136b824)

Create new roles with desired attributes in one call.

(cherry picked from commit ae4116c0f6)
2018-02-22 15:50:35 -05:00
Adarnof
1ce041b90a Prevent new roles from being sorted separately.
Addresses #969

(cherry picked from commit 3080d7d868)
2018-02-22 14:44:48 -05:00
Adarnof
91ec924acc Ensure api backoff returns result of decorated function 2018-02-22 02:08:32 -05:00
Adarnof
0f1535161c Handle HTTP429 on nickname API endpoint
Closes #971

(cherry picked from commit a64dda2a2e)
2018-02-21 17:52:32 -05:00
Adarnof
1caa4b6baa Merge pull request #973 from soratidus999/timerupdates
Updated Structure Choices
2018-02-21 17:20:22 -05:00
Ariel Rin
0474fa6d17 Updated Strucure Choices
Added Refineries, and a Moon Mining Option
Also changed spacing to be consistent and be easier to read
2018-02-21 23:01:08 +10:00
Adarnof
e1907d9d17 Do not localize comment count
Closes #910
2018-01-07 21:07:53 -05:00
ghoti
2e214e442c Sort Completed HR apps by create date (most recent first) (#931) 2017-12-20 18:03:51 -05:00
Adarnof
0d64441538 Version bump to v1.15.6 2017-11-17 16:22:03 -05:00
Adarnof
58a333c67a Case-insensitive group name to ID translation
Seems Discourse won't let you create `Group` if `group` already exists (`422 Name has already been taken`).

Thanks @huberfe
2017-11-17 13:07:41 -05:00
Adarnof
6837f94e59 Disable SeAT accounts instead of deleting. (#915)
See eveseat/web@1abb402
2017-11-03 19:20:31 -04:00
phaynu
16987fcaf0 Extending Choices for Questions in hrapplications to Allow Multiselect (#911)
An additional field at the question level defines whether the choices for the question are multi-select or not. The template will render the choices with radio buttons or checkboxes depending on multi-select. Multiple selected choices are saved with a line break between them.
2017-10-25 00:35:19 -04:00
Derptron
ebd3be3f46 Documentation update (#850)
* Update to the Dependency in regard to using SeAT
* Update to the installation of SSL-Certificates with Discourse
* CleanUp of some missing information in the discourse section
2017-10-05 13:15:34 +10:00
Adarnof
a02e5f400a Version bump to v1.15.5 2017-10-03 22:37:56 -04:00
Adarnof
65c168939d Handle FAT ZeroDivisionErrors
Closes #881
2017-10-03 21:50:32 -04:00
Adarnof
313cac6ac7 Handle new zKillboard API format
Closes #872
2017-10-01 12:53:03 -04:00
Adarnof
0145ea82c8 Correct py3 __str__ support.
Change slugify package for py3
2017-09-30 18:38:05 -04:00
Adarnof
0cdc5ffbd5 Use pypi versioned adarnauth-esi 2017-09-27 18:52:48 -04:00
Basraah
0bdd044378 Improve support for milliseconds backoff 2017-09-26 09:02:37 +10:00
Adarnof
ad266ea2ee Increase tested retry after
Apparently tests take longer than 200ms to evaluate here.
2017-09-25 18:36:53 -04:00
Adarnof
7ea8c9e50d Retry after in milliseconds
Closes #874
2017-09-25 18:21:23 -04:00
mmolitor87
9a015fd582 Change index images to font (#841)
* Change index images to font

* Added SEAT_URL reference and added it to the index template
2017-09-23 08:29:08 +10:00
Adarnof
7ca1c87c87 Minimize swagger spec files. 2017-09-20 01:31:04 -04:00
Adarnof
eee6a9132d Use local swagger spec files (#866)
Allows auth to keep working if CCP changes "latest" definition.
Requires adarnauth-esi>=1.4
2017-09-17 01:08:02 -04:00
Basraah
9d90af4a3d Fixes #865 & adds unit tests 2017-09-13 20:16:07 +10:00
Adarnof
72305de2d8 Correct username hashing on py3 2017-09-13 00:29:59 -04:00
Adarnof
8f58f76001 Stop using v3(dev) Alliance resource
It keeps changing.
2017-09-12 20:16:15 -04:00
Adarnof
a969b6117b Fix missing operation in v3 alliance resource 2017-09-12 12:34:43 -04:00
Basraah
97762119b3 Remove .idea folder 2017-09-12 11:33:42 +10:00
Adarnof
4bdead5ef2 Version bump to v1.15.4 2017-09-11 20:52:07 -04:00
Adarnof
8987cf2199 Use Django's cache framework for service group names (#857)
Use django-redis-cache backend for locking get_or_set
No longer require group-related tasks to be locked to one simultaneous execution.
Remove legacy service group cache models.

Truncate Discord nicknames to 32 characters
Correct Discourse group name extension using only valid leading characters.
Prevent name slicing from ending with illegal character

Closes #801
Closes #847
Closes #835
Closes #852
2017-09-11 20:42:13 -04:00
Basraah
27c9b09116 Add encoding declaration to prevent #819 2017-09-11 12:59:13 +10:00
Adarnof
0ac0f71fef Correct SeAT API logic (#860)
* Do not attempt to change user email on SeAT if unchanged.
This prevents HTTP422 from being raised on password resets.

*Delete users on deactivation.
The existing disable user logic does nothing and results in a HTTP500.
It's safe to delete users entirely - the API keys are retained.

Fixes #844
2017-09-08 13:42:35 -04:00
Basraah
3f454743a9 Openfire group names fix (#859)
* Force lowercase group names

* Fix comparison of group names

* Sanitise group name for broadcast message
2017-09-05 13:12:27 -04:00
Basraah
c2f12eed26 Fixes #753 2017-09-05 14:46:38 +10:00
Basraah
1b1b692ac0 Add missing block tag 2017-09-03 09:09:48 +10:00
mmolitor87
dc8ed2d510 Supervisor celery simplification (#849)
Combines celery confs under one process group for easy restarting. Updates docs to reflect new commands.
2017-09-01 15:43:54 -04:00
Basraah
049c1c66aa Make Fleet-up datetimes timezone aware (#856)
Additionally fix logger depreciation warnings
2017-09-01 15:41:45 -04:00
Adarnof
8028660a8f Return tuple on SeAT activation failure
Addresses #844
2017-09-01 00:40:22 -04:00
ghoti
e6532025f8 fixes #848 - could be bug, could be permanent unclear change from CCP (#854) 2017-09-01 00:29:31 -04:00
cameronurnes
3361d36bbf Allow preset choices for questions in HR (#836) 2017-09-01 00:25:15 -04:00
Basraah
2ab45b1019 Fix cache type error with retry time 2017-07-26 08:59:22 +10:00
Adarnof
c5b55283d1 Version bump to v1.15.3 2017-07-19 22:07:36 -04:00
ghoti
d937d5b5d4 Use primary key instead of user object for api_refresh 2017-07-11 21:39:29 -04:00
Adarnof
b41521bcb5 Version bump to v1.15.2 2017-06-27 00:21:00 -04:00
Basraah
882cafb4ba Discord API rate limiting (#799)
Added discord too many requests handling decorator
Added tests for core Discord manager functions
Added discord backoff retry for celery
Added tests for update groups backoff
Support per-route and global rate limiting
2017-06-04 18:36:25 -04:00
Basraah
5db340c64a Fleet-up cleanup (#798)
Add caching and better error handling
Move fleetup templates into fleetup app
Move fleetup urls into fleetup app
Fix button overflow and url
Add manager unit tests
Fix python3 compatibility
2017-06-04 18:35:23 -04:00
Adarnof
e76b0789f3 correct centos service name
Thanks @iAddz
2017-05-23 11:03:52 -04:00
iAddz
d2e32d3da3 Fleet op timer ui update (#787) 2017-05-11 12:32:45 -04:00
Adarnof
12cfc552da Correct Corporation creation using XML provider
Closes #734
2017-05-04 22:41:27 -04:00
Adarnof
dc10245158 Do not suspend account on disabling user. 2017-05-04 22:31:56 -04:00
iAddz
1b0c3c3bfc Corp member view for fat stats (#785)
Additionally fixes 500's from showing when
- a corporation object has been deleted
- an account doesn't have a main char set

Add Math tooltips
2017-05-04 10:39:03 -04:00
Basraah
aec013b93c SeAT service cleanup (#796)
Manager function tidyup
Hopefully improved the key sync function, at least it should be easier to follow whats happening now.
Remove partial logging of unhashed passwords
Added user feedback
2017-05-04 10:31:57 -04:00
iAddz
4556a0e740 SRP QOL + Validation (#786)
- new UI for srp management (mass performs, inline editing)
- unique validation for srp killboard links
- character auth ownership verification for killboard links
- removed remnants of old error messaging system & replaced with current
standard
 - added a confirmation popup when deleting fleet SRP's
2017-05-03 16:55:10 -04:00
mmolitor87
aad3bd6f57 sets language value to default for phpbb (#771)
Without this being set users get "The language you specified is not
valid." when trying to edit global settings such as timezone or style.
2017-05-03 16:53:44 -04:00
Basraah
17dd7c04c7 Replace django-celery with base Celery (#791)
Update celery tasks to new style & remove djcelery
Vanilla celery + django-celery-beat take over the role of djcelery. Task schedules are consolidated into settings instead of residing in code.
Update docs and example supervisor configs.
2017-05-03 16:53:16 -04:00
Basraah
372e582c6e Nginx docs (#794) 2017-05-03 16:50:27 -04:00
Adarnof
5a93128f4f Prevent FAT CorpStats creation for missing corp models. 2017-04-27 22:29:13 -04:00
Basraah
901dd5033a Added a cut down apache config 2017-04-27 10:28:06 +10:00
Basraah
d8043ff735 Add Gunicorn docs (#777)
* Added gunicorn docs

* Changes suggested by @Betriebsrat
2017-04-11 11:53:12 +10:00
Adarnof
bb3e7a0449 Tolerate validating submitted email if 2+ users
Addresses #783
2017-04-10 14:27:17 -04:00
Basraah
806962cda5 Fix incorrect variable name 2017-04-02 21:50:48 +10:00
Basraah
2cd43280e2 Remove obsolete services settings 2017-03-31 13:22:43 +10:00
Adarnof
6c94640552 Set main_char_id to emptystring when main deleted
Closes #769
2017-03-18 21:30:17 -04:00
Adarnof
250c376abb Correct queuing of name syncs with user pk 2017-03-12 16:06:14 -04:00
Adarnof
fb22aaf731 Consolidate TS3 into base services table
Beautify services table with hover and no borders
Unify formatting of mumble/jabber/ts3 service URLs in table
2017-03-08 17:50:43 -05:00
Adarnof
9897c0bbba Provide mumble quick join link
Add button titles to services
2017-03-08 17:31:41 -05:00
Adarnof
7d0aa2b5ec Wait to validate user is on TS after presenting form (#758) 2017-03-07 23:41:48 -05:00
Adarnof
27628dc70b include http in front of example settings
@porowns
2017-03-07 16:28:19 -05:00
Basraah
ecb74e67b0 Fix link format 2017-02-28 12:41:59 +10:00
Basraah
de47e94870 Version bump to 1.15.1 2017-02-28 11:39:25 +10:00
Basraah
9238ac97cf Remove unnecessary package 2017-02-28 11:38:47 +10:00
Basraah
2e274d3baf Update Openfire broadcast tool (#742)
Allow users to ignore invalid certificates.
Added some limited user feedback.
Removed threading.
Prevent infinite connection attempt loops.
2017-02-28 11:30:26 +10:00
Basraah
c6118beddf Teamspeak 3 Updates (#741)
* Correct duplicate error and success messages to user

* Read out all buffer bytes before sending command

* Convert ts3 manager to use a single connection

Each instance of the class will now use a single connection and should
be cleanly disconnected when finished.

Compatible with `with` clauses and will automatically disconnect from
the TS3 server when it exits the `with` block.

* Update TS3 manager consumers to use new style

* Update unit tests to use new style manager
2017-02-28 11:28:51 +10:00
Nathan Morgan
e6e1339d71 Added Google reCaptcha (#738) 2017-02-28 11:27:24 +10:00
Basraah
693016e171 CDN Javascript & CSS fix and cleanup (#743)
* Add missing javascript and css

* Remove unnecessary javascript and css
2017-02-27 19:29:05 +10:00
Basraah
3e09f2179f Fix translation
Closes #740
2017-02-26 19:37:26 +10:00
Adarnof
3a1d0d0335 use v1 Universe for structure names 2017-02-25 17:24:12 -05:00
Adarnof
7c14aede26 Correct ObjectNotFound message for ESI corp IDs
Addresses #732
2017-02-23 17:32:09 -05:00
Adarnof
308dc9191f Create v1 Character ESI client for names lookup
Closes #731
Alter member sorting to put registered characters ahead of unregistered
Conditionally pluralize Main Characters on template
2017-02-23 10:42:26 -05:00
Adarnof
1a958384c3 Include images instead of imgur links 2017-02-22 23:52:27 -05:00
Adarnof
078ec785e4 Alter docs link to default version 2017-02-22 23:35:53 -05:00
Basraah
97c725f58f Version bump to 1.15.0 2017-02-23 12:54:18 +10:00
Basraah
ed52f4f411 Change default database usernames
Makes the source of SQL error messages more clear
2017-02-23 11:08:16 +10:00
Basraah
4c1c33fa23 Add documentation for groups (#728) 2017-02-21 17:10:00 +10:00
Adarnof
3d15cb9c57 Prevent refresh from resubmitting comments (#715)
Closes #711
2017-02-20 23:24:19 -05:00
iAddz
e3e84158f8 Corputils Changes (#668)
Order members by main characters

Display unique user count
2017-02-20 23:22:01 -05:00
Basraah
2d6c641648 IPS4 and Market PHP hash fix (#727)
Force bcrypt 2y for PHP apps

2b isn't supported by older versions of PHP supplied by e.g. Ubuntu
14.04. 2a is insecure.

Remove plaintext warning

No services store plaintext passwords anymore.

Switch form to password field
2017-02-20 23:20:12 -05:00
Adarnof
5cec2f834b Hard-code ESI resource versions (#726)
Should prevent #718
2017-02-20 23:19:39 -05:00
Basraah
c0b1523f39 Fix translation tags 2017-02-20 12:24:15 +10:00
Basraah
f6ea9e0236 Force bcrypt version 2a
Insecure, but 2b is not supported by IPS4 according to user reports. This manager needs to be changed to use the IPS4 API at some point anyway, so really a stop gap measure.
2017-02-20 12:10:15 +10:00
Basraah
fd05eff5d4 Add missing trailing slash in API URL 2017-02-20 12:01:37 +10:00
Basraah
dfd416c38d Apply ESI endpoint changes (#719)
And some more logging so the problem is obvious next time
2017-02-14 23:07:03 -05:00
Basraah
3af1a6a1fb Correct scheduled task calls 2017-02-13 13:45:50 +10:00
Basraah
7ce47547ba Version bump 2017-02-12 19:27:26 +10:00
Basraah
fe9da4d5de Add fake initial
Will cause Django to fake any initial migrations for tables that already exist. Required as we have moved some tables to other apps.

If the tables don't exist, the initial migrations run as normal.
2017-02-12 17:23:33 +10:00
iAddz
a6c48f8d71 Localisation & German translation updates (#701) 2017-02-11 23:04:18 -05:00
Basraah
a33c8c14ee Grant service access by permissions (#692)
* Add service access permissions and migration

`ENABLE_AUTH_<servicename> = True` will have the new permission applied
to the settings configured `DEFAULT_AUTH_GROUP` group or `Member` if
none is configured.

`ENABLE_BLUE_<servicename> = True` will have the new permission applied
to the settings configured `DEFAULT_BLUE_GROUP` group or `Blue` if none
is configured.

* Move views and hooks to permissions based access

* Remove access restriction to services view

Hypothetically non-member/blues could be granted permission to access
services manually as desired now. A user that has no permissions to
access any services will see a blank services list.

* Remove obsolete service settings

* Remove references to obsolete settings

* Adjusted tests to support permissions based access

* Fix incorrectly named permissions

* Add simple get_services generator function

* Added signals for user and groups perm changes

* Update validate_services to support permissions

deactivate_services removed as its surplus to requirements.

* Removed state parameter from validate_services calls

* Update tests to support signals changes

* Fix incorrect call to validate_services task

* Fix validate_services and test

* Add validate_user to changed user groups signal

* Added tests for new signals

* Remove unnecessary post_add signals

* Added documentation for service permissions

* Added detection for members with service active

If there are any service users in the Member or Blue groups active, then
the permission will be added to the respective Member or Blue group.
This means its no longer necessary to maintain the service enablesettings to migrate to permissions based service.

Remove obsolete state based status checking
2017-02-11 22:51:30 -05:00
Basraah
58e121d10d Replace third party client libraries with CDN delivered versions (#709)
* Correct invalid html

* Add bundle files for CDN CSS and javascript

* Replace static javascript refs with bundles

* Change password reset templates to use a basefile

And switch to use bundles

* Remove third party libraries

* Remove awkward margin styling on navbars
2017-02-11 22:06:10 -05:00
Adarnof
8c82897a92 Support static blue corps and alliances (#708) 2017-02-11 22:05:32 -05:00
Basraah
d7291f83c3 Add Teamspeak 3 Py3 compatibility (#705)
Replace sockets with telnetlib
Provides Python 2 and 3 compatibility
2017-02-11 22:05:18 -05:00
Basraah
914c204a40 SeAT Manager (#704)
* SeAT service in modular service app format

* Replace string concatenation with formatters

* Fix incorrect references to user

* Fix exception when user doesn't have seat active

* Prevent deletion of seat API keys by default

* Improve api response error handling

* Corrected notification message

* Added missing view returns

* Update SeAT to use permissions based access

* Update password generator to new style

* Correct logging message

* Fix seat role update tasks

* Correct validate user logic

* Add seat test settings

* Added basic seat unit tests

* Added add permissions function from other branch

* Remove obsolete settings references
2017-02-11 22:04:47 -05:00
Basraah
918ecf812c Publically joinable Groups (#697)
* Add public field to AuthGroup

* Add permission for users to join non-public groups

By default this permission will be applied to the "Member" group to
maintain the current behaviour.

* Allow users to join public groups

Users without the 'groupmanagement.request_groups' permission will be
able to join groups marked as public but will not be able to see or join
any other groups.

* Prevent None state change from purging groups

Currently when a user drops from Blue or Member state all groups and
permissions are discarded. This softens that approach by not removing
public groups and creates a distinction between the two activities. An
argument could maybe be made for not removing permissions on a state
change, but that is beyond the scope of this change.

* Correct syntax for removing filtered groups

* Add unit tests for disable user and member

* Update services signals tests

* Correct mocking call

* Remove permissions checking from menu item
2017-02-11 22:03:39 -05:00
Basraah
b636262e0c Permissions Auditing Tool (#698)
* Added block for page_title as title fragment

* Add permissions auditing tool

* Added tests for permissions audit tool

* Added documentation for permissions tool

* Add permissions tool to coverage
2017-02-10 21:25:09 +10:00
Basraah
489b9a601d Implement Openfire username escaping (#703)
* Fix openfire username sanitize function

* Use escaping instead of stripping characters
2017-02-10 13:30:57 +10:00
Basraah
ff1c2030ca Update optimer and fleetup javascript (#695)
* Split out moment duration formatter

* Replace fleetup timer js

* Replace optimer js

* Remove obsolete countdown.js library

* Remove unused dateformat.js references

* Remove obsolete dateformat library
2017-02-07 19:04:31 +10:00
Basraah
1a749bf712 Update timerboard javascript (#688)
* Fix and update javascript

Reformatted javascript.
Updated javascript to reduce duplicate code.

* Replace countdown and dateformat with moment

Improves i18n
Fixes #685
2017-02-04 09:41:18 +10:00
Basraah
3f8b7f7fc1 Version bump to 1.15-alpha 2017-02-03 22:00:34 +10:00
nizzan
0902e250d3 Update pip packages during auth update (#681) 2017-02-03 00:08:52 -05:00
Basraah
c0885fa7c5 Stop search from failing with None alliance name (#684) 2017-02-02 13:23:23 -05:00
Basraah
038f948aab Specify short names for service tasks (#682) 2017-02-01 14:28:43 +10:00
Basraah
d7a4d06120 Prevent market url patterns from conflicting
Addresses #683

Install instructions have people aliasing `/market/` to the alliance market app which prevents access to the market action URLs.
2017-01-30 16:46:35 +10:00
Basraah
13204a7e91 Fixed incorrect parameters for task apply call (#680)
Added test to ensure member groups are correctly applied after
`activate_mumble` is called.
2017-01-29 10:46:51 +10:00
Basraah
3d645867bb Fix typo 2017-01-29 09:52:01 +10:00
Basraah
aa0148f23b Services Modules migration improvements (#676)
* Added a check to prevent dataloss from missing service modules

Migration will raise an exception and refuse to run if a model has data
for a field which is missing its target service.

* Add setting to allow services to be installed after service migration

Previously django would complain about migrations being out of order. By
setting `SERVICES_MIGRATED=True` the
`authentication.0013_service_modules` migration drops all of its
'optional' dependencies which allows the initial migrations of service
modules to run normally. If the setting is missing or set to False, the
migration will require all installed and required services migrations to
have run before the `authentication.0013_service_modules` migration.

* Move setting to somewhere it makes more sense

* Modify celerybeat registration to automatically register services
2017-01-28 18:15:58 +10:00
Basraah
ed32925525 Fix discourse example sso url 2017-01-28 10:34:01 +10:00
Basraah
4f7c7ca1c6 Fix example discord callback url 2017-01-28 10:31:46 +10:00
Basraah
35ceb53936 Fix some incorrect service example URLs 2017-01-28 08:53:49 +10:00
Basraah
c4c6d13d36 Documentation for Service Modules (#677)
Added documentation for writing service integrations
Added menu hook documentation
Added notes about installing service modules before following service installation guide
2017-01-27 11:20:06 -05:00
Adarnof
44256c3408 Remove legacy upgrade instructions. 2017-01-25 17:23:17 -05:00
Basraah
2c68f485e2 Upgrade Mumble password hashing to bcrypt (#671)
Added transition to bcrypt-sha256 hashing for mumble passwords.
All new passwords will be hashed by bcrypt-sha256. The existing SHA-1
hashes will continue to work as a fallback for legacy password hashes.
2017-01-25 15:10:07 -05:00
Basraah
11d52d476c Added build and coverage badges to readme 2017-01-25 13:08:53 +10:00
Basraah
1066e6ac98 The Great Services Refactor (#594)
* Hooks registration, discovery and retrieval module

Will discover @hooks.register decorated functions inside
the auth_hooks module in any installed django app.

* Class to register modular service apps

* Register service modules URLs

* Example service module

* Refactor services into modules

Each service type has been split out into its own django app/module. A
hook mechanism is provided to register a subclass of the ServiceHook
class. The modules then overload functions defined in ServiceHook as
required to provide interoperability with alliance auth. Service modules
provide their own urls and views for user registration and account
management and a partial template to display on the services page. Where
possible, new modules should provide their own models for local data
storage.

* Added menu items hooks and template tags

* Added menu item hook for broadcasts

* Added str method to ServicesHook

* Added exception handling to hook iterators

* Refactor mumble migration and table name

Upgrading will require `migrate mumble --fake-initial` to be run first
and then `migrate mumble` to rename the table.

* Refactor teamspeak3 migration and rename table

Upgrading will require `migrate teamspeak3 --fake-initial`

* Added module models and migrations for refactoring AuthServicesInfo

* Migrate AuthServiceInfo fields to service modules models

* Added helper for getting a users main character

* Added new style celery instance

* Changed Discord from AuthServicesInfo to DiscordUser model

* Switch celery tasks to staticmethods

* Changed Discourse from AuthServicesInfo to DiscourseUser model

* Changed IPBoard from AuthServicesInfo to IpboardUser model

* Changed Ips4 from AuthServicesInfo to Ips4User model

Also added disable service task.

This service still needs some love though. Was always missing a
deactivate services hook (before refactoring) for reasons I'm unsure of
so I'm reluctant to add it without knowing why.

* Changed Market from AuthServicesInfo to MarketUser model

* Changed Mumble from AuthServicesInfo to MumbleUser model

Switched user foreign key to one to one relationship.
Removed implicit password change on user exists.
Combined regular and blue user creation.

* Changed Openfire from AuthServicesInfo to OpenfireUser model

* Changed SMF from AuthServicesInfo to SmfUser model

Added disable task

* Changed Phpbb3 from AuthServicesInfo to Phpbb3User model

* Changed XenForo from AuthServicesInfo to XenforoUser model

* Changed Teamspeak3 from AuthServicesInfo to Teamspeak3User model

* Remove obsolete manager functions

* Standardise URL format

This will break some callback URLs
Discord changes from /discord_callback/ to /discord/callback/

* Removed unnecessary imports

* Mirror upstream decorator change

* Setup for unit testing

* Unit tests for discord service

* Added add main character helper

* Added Discourse unit tests

* Added Ipboard unit tests

* Added Ips4 unit tests

* Fix naming of market manager, switch to use class methods

* Remove unused hook functions

* Added market service unit tests

* Added corp ticker to add main character helper

* Added mumble unit tests

* Fix url name and remove namespace

* Fix missing return and add missing URL

* Added openfire unit tests

* Added missing return

* Added phpbb3 unit tests

* Fix SmfManager naming inconsistency and switch to classmethods

* Added smf unit tests

* Remove unused functions, Added missing return

* Added xenforo unit tests

* Added missing return

* Fixed reference to old model

* Fixed error preventing groups from syncing on reset request

* Added teamspeak3 unit tests

* Added nose as test runner and some test settings

* Added package requirements for running tests

* Added unit tests for services signals and tasks

* Remove unused tests file

* Fix teamspeak3 service signals

* Added unit tests for teamspeak3 signals

Changed other unit tests setUp to inert signals

* Fix password gen and hashing python3 compatibility

Fixes #630

Adds unit tests to check the password functions run on both platforms.

* Fix unit test to not rely on checking url params

* Add Travis CI settings file

* Remove default blank values from services models

* Added dynamic user model admin actions for syncing service groups

* Remove unused search fields

* Add hook function for syncing nicknames

* Added discord hook for sync nickname

* Added user admin model menu actions for sync nickname hook

* Remove obsolete code

* Rename celery config app to avoid package name clash

* Added new style celerybeat schedule configuration

periodic_task decorator is depreciated

* Added string representations

* Added admin pages for services user models

* Removed legacy code

* Move link discord button to correct template

* Remove blank default fields from example model

* Disallow empty django setting

* Fix typos

* Added coverage configuration file

* Add coverage and coveralls to travis config

Should probably use nose's built in coverage, but this works for now.

* Replace AuthServicesInfo get_or_create instances with get

Reflects upstream changes to AuthServicesInfo behaviour.

* Update mumble user table name

* Split out mumble authenticator requirements

zeroc-ice seems to cause long build times on travis-ci and isn't
required for the core projects functionality or testing.
2017-01-25 12:50:16 +10:00
Adarnof
5738b015c3 Correct typo in exception handling 2017-01-23 23:28:55 -05:00
Adarnof
88534837e2 Merge pull request #665 from allianceauth/provider
Correct startup error when provider settings blank
2017-01-22 12:27:21 -05:00
Adarnof
026b4c272b Correct startup error when settings blank 2017-01-22 12:15:32 -05:00
Adarnof
294cd6b781 Remove references to legacy jacknife setting 2017-01-20 10:00:24 -05:00
Adarnof
0292ad07ad Customizable API auditing URLs via template filter (#652)
Closes #636
2017-01-19 23:01:26 -05:00
Adarnof
f5cb6a3fb7 Version bump to 1.14.2 2017-01-19 19:22:30 -05:00
Adarnof
0b57e027f7 Variable itemtype name datasources (#662)
Make provider settings optional
Wait to initialize adapter until first external call
Abstract get methods from adapter
2017-01-19 19:19:20 -05:00
Adarnof
3d50f977d9 Remove leftover IS_CORP references 2017-01-18 23:25:42 -05:00
Adarnof
0abb160886 Update links for new repo's new organization 2017-01-18 22:48:37 -05:00
Adarnof
ff3bb9743f Enforce unique IDs and names for eveonline models (#657)
Should help narrow down #656. Please report IntegrityErrors.
2017-01-18 19:52:04 -05:00
Adarnof
cf81f59fa6 Add missing titles to SRP buttons (#654)
Add pending request counter.
Restrict SRP status to choices.

Closes #643
2017-01-18 15:46:47 -05:00
Adarnof
d186088a8f Update universe types call to v2 format 2017-01-18 12:03:12 -05:00
Adarnof
2106e492e3 Adjust hierarchy for doc indexes 2017-01-18 00:46:09 -05:00
Adarnof
badc5a1f7f Include troubleshooting steps. 2017-01-18 00:43:13 -05:00
Adarnof
cced434a99 Correct API key form validation errors.
If an individual field fails, its value gets removed from the cleaned_data dict, but the full form clean() method still gets called.
As this form checks for api_id and api_key in the dict upon clean(), it would raise an unhandled KeyError
2017-01-17 23:49:55 -05:00
Adarnof
6c4e8ec2d1 Ensure correct corp and alliance groups are assigned.
Closes #650
2017-01-15 22:49:33 -05:00
ixof
5bbaef4476 Update Dashboard Image URL Scheme to HTTPS (#648) 2017-01-15 17:24:44 -05:00
Adarnof
6208071537 Include gcc-c++ CentOS dependency.
#645
2017-01-14 16:17:06 -05:00
Adarnof
df02982983 Login newly registered users.
Closes #642
2017-01-14 02:28:13 -05:00
Adarnof
eaa9d1930c Update documentation to allow removal of wiki pages. 2017-01-13 23:16:44 -05:00
Adarnof
24bc9d4b7f Provide feedback when ESI errors occur.
Closes #620
2017-01-13 21:56:27 -05:00
Adarnof
73641a7a1e Merge remote-tracking branch 'origin/master' 2017-01-13 21:33:18 -05:00
Adarnof
485c0fc373 Periodic task to update all corpstats every 6 hours.
Closes #617
2017-01-13 21:33:02 -05:00
Adarnof
55cbbadc2b Cache EVE API objects using django-redis to speed repeated queries. (#638) 2017-01-13 20:56:41 -05:00
Adarnof
b1dafeda8d Pathfinder setup instructions. 2017-01-13 20:53:58 -05:00
Adarnof
380069627a Correct Jacknife SQL step. 2017-01-13 19:17:50 -05:00
Adarnof
c3390b089e Include installation instructions for Jacknife
Update teamspeak binary links.
2017-01-13 19:13:07 -05:00
Adarnof
c9d9b0bf07 Install instructions fetch latest auth release 2017-01-12 21:11:04 -05:00
Adarnof
d4064fd059 Version bump to 1.14.1 2017-01-12 00:17:20 -05:00
Adarnof
dd63ff7884 Add supervisor installation instructions.
Closes #521
2017-01-11 22:58:42 -05:00
Adarnof
5e9a782c99 Correct CentOS installation instructions.
Closes #625
Closes #621
2017-01-11 22:29:51 -05:00
Adarnof
9865726d2d Correct documentation structure. 2017-01-11 22:10:15 -05:00
Adarnof
e8915b84e5 Get ship type name from ESI
Closes #631
2017-01-11 21:58:20 -05:00
Adarnof
8360371ab7 Enforce unique AuthServicesInfo (#618)
Alter user field to OneToOneField
Migration to enforce uniqueness pre-change
Migration to ensure all users have an AuthServicesInfo
Receiver to automatically create one upon user creation
Replace AuthServicesInfo.get_or_create with get
Prevent deletion of AuthServicesInfo from admin site
Remove add and delete permissions from model.

Get character names in chunks on corpstats update to prevent HTTP400 when requesting >350(ish) names

Include corpstats docs.
Update settings docs.
2017-01-11 21:48:20 -05:00
Adarnof
33c2ba9bca Change default log name to avoid conflict
Allows use of supervisor conf without changes
2017-01-10 21:53:53 -05:00
Adarnof
ab3c671d53 Only initialize clients specified in settings 2017-01-07 23:30:56 -05:00
Adarnof
4c72352737 Get character names in chunks on corpstats update
Prevents HTTP400 when requesting >350(ish) names
2017-01-06 22:32:41 -05:00
Adarnof
c4a6492ab5 Cache related objects to speed queries (#616) 2017-01-06 01:10:12 -05:00
Adarnof
e1ccd972e4 Clean up readme. Add rtfd badge. 2017-01-06 01:10:41 -05:00
Basraah
9ad61c1f4c Move documentation into repository (#613) 2017-01-06 00:11:24 -05:00
Adarnof
8e64fe145e Correct state migration for multitenant 2017-01-05 23:51:49 -05:00
Adarnof
395c2c1bec Correct ID type mismatch on API key refresh. 2017-01-05 00:43:52 -05:00
Adarnof
3d25f58fbb Correct missing ticker. 2017-01-05 00:23:06 -05:00
Adarnof
1887d612e6 Only trigger group update for saved users 2017-01-03 20:43:35 -05:00
Adarnof
a561862911 Correct TS group update on perm key refresh 2017-01-03 20:12:29 -05:00
Adarnof
a309a733d6 Hide alliance logo when main character has none. 2017-01-03 18:33:30 -05:00
Adarnof
d964197b27 Correct missing permission names. 2017-01-03 18:05:35 -05:00
Adarnof
653bb97c55 Correct standing model creation 2017-01-03 13:18:04 -05:00
Adarnof
02aaa7ff78 Version bump to 1.14 2017-01-03 00:44:42 -05:00
Adarnof
c205bff99f Remove references to optional SSO configuration 2017-01-02 20:55:12 -05:00
Adarnof
56082848a7 EVE Swagger Interface (#591)
FAT uses ESI tokens to get character location/ship
 - closes #564

 Pull corp memebrship data from ESI

Additional permissions for non-api viewing.
 - migration to convert permissions from old users.

Standardize EVE datasource responses.
 - allow different sources for EVE data types.

Allow empty values for character alliance id and name

Allow multiple corps and alliances to be considered 'members'
2017-01-02 20:50:21 -05:00
Adarnof
2816a5fa46 Change admin site header to version number (#611) 2017-01-02 20:24:41 -05:00
Adarnof
4f26fa03e6 Correct navbar highlighting for group management. 2017-01-02 18:54:37 -05:00
Basraah
4ae450f963 Group system overhaul (#588)
* Add open/hidden group membership display and remove

* Include requestable groups other than open

* Prevent users requesting or leaving non-joinable groups

I have not prevented users joining hidden groups however, as
there may be some use cases where the direct link is provided
for users to request access to the group.

Also prevent users generating leave requests for groups they
are not a member of.

* Refactor Group extension models into a single OneToOne model

Added group leader field

* Add blankable fields

* Switched to use navactive for menu highlighting

* Consolidate member state checking for easier code reuse

* Added support for group leaders to manage groups

* Added info log when a user removes someone from a group

* Add ordering to group member list
2017-01-02 18:52:20 -05:00
Adarnof
959e167987 Remove references to legacy character management. 2017-01-02 18:51:19 -05:00
Basraah
f96959d854 Replace API Key page with Dashboard profile (#582)
Shows characters on each API key, main character, API key
status and the user groups.
2017-01-02 18:47:10 -05:00
Adarnof
2f5529b582 Remove refernces to optional SSO configuration 2016-12-29 18:34:21 -05:00
Adarnof
04f2731517 Multitenant
Closes #604
2016-12-29 18:10:43 -05:00
Adarnof
71c1054328 EVE Swagger Interface
Closes #591

Conflicts:
	authentication/views.py
	eveonline/views.py
	stock/templates/public/characternotexisting.html
2016-12-29 17:53:33 -05:00
Adarnof
917707027e Correct navbar highlighting for group management 2016-12-29 17:49:40 -05:00
Adarnof
03aab76ab9 Group system overhaul
Closes #588
2016-12-29 17:42:10 -05:00
Adarnof
1e523d762e Remove references to legacy character management.
Correct group name spacing.
2016-12-29 17:41:00 -05:00
Adarnof
33e481fb12 Replace API Key page with Dashboard profile
Closes #582
2016-12-29 17:13:07 -05:00
Adarnof
16a4ed9bf2 Correct site name template syntax 2016-12-21 23:40:45 -05:00
Sebastian
286cb9e1d1 added citadels and engineering complexes to timerboard (#602) 2016-12-20 22:59:02 -05:00
Adarnof
2f2c2bd0e5 Complete removal of single tenant aspects. 2016-12-20 07:17:21 +00:00
Adarnof
3cdbff6b36 Merge branch 'esi' of https://github.com/r4stl1n/allianceauth into multitenant 2016-12-20 06:54:40 +00:00
Adarnof
54464c23cf Begin removal of IS_CORP 2016-12-20 06:54:29 +00:00
Adarnof
2a87eed059 Reduce API calls to speed up API checks 2016-12-19 21:13:45 -05:00
Adarnof
55f349c35b Remove legacy xml functions. 2016-12-19 21:07:45 -05:00
Adarnof
604808b195 Retrieve character objects from api keys. 2016-12-19 20:56:14 -05:00
Adarnof
ce35e72e44 Correct discourse group sync task calling. 2016-12-20 01:20:31 +00:00
Adarnof
8582584fd5 prevent redis connectionerror for async tasks 2016-12-19 17:33:11 -05:00
Adarnof
8379fdd7d5 Update corp/alliance models using adapters. 2016-12-17 20:51:59 -05:00
Adarnof
ec9b43b083 Allow different sources for EVE data types. 2016-12-17 14:46:02 -05:00
Adarnof
66240ad296 Standardize EVE datasource responses.
Remove alliance member count.
2016-12-16 22:13:29 -05:00
Adarnof
0fe5a1c5e3 Correct context processor for SSO address. 2016-12-16 17:43:44 -05:00
Adarnof
b514f8cbcc Correct missing migration dependency. 2016-12-14 20:57:56 -05:00
Adarnof
4ee10e0c31 Additional permissions for non-api viewing.
Migration to convert permissions from old users.
2016-12-14 20:40:12 -05:00
Adarnof
5f88e7e1a5 Correct permission check for showing APIs 2016-12-13 20:22:09 -05:00
Adarnof
5a9418d792 Visual indication of member registration. 2016-12-13 19:29:02 -05:00
Adarnof
02bd4570b0 Default to only available corpstats if no corp_id 2016-12-13 17:45:42 -05:00
Adarnof
6ba084c710 Cleanup FAT edit page tables with pagination.
Cleanup FAT statistics generation with smarter query.
2016-12-13 17:31:36 -05:00
Adarnof
6fd3c32ba0 Correct check if corputils is visible to user.
Add additional admin site permissions.
2016-12-13 17:30:06 -05:00
Adarnof
3d92e4c5c5 Perform first corpstats update on creation. 2016-12-13 16:26:20 -05:00
Adarnof
27fc8a373f Pull corp memebrship data from ESI 2016-12-13 16:11:06 -05:00
Adarnof
32009fd3ff Move FAT templates to app folder.
Provide feedback via messages.
2016-12-11 23:26:20 -05:00
Adarnof
b4b739ee61 FAT uses ESI tokens to get character location/ship
Closes #564
2016-12-11 22:10:36 -05:00
Adarnof
a630b5b397 Migrate to adarnauth-esi 2016-12-11 20:11:05 -05:00
Basraah
721708ee16 Add ordering to group member list 2016-12-08 12:50:45 +10:00
Basraah
4dabb3198d Merge branch 'group-membership' into group-refactor 2016-12-06 20:20:43 +10:00
Basraah
c0ca73f9ea Added info log when a user removes someone from a group 2016-12-06 20:18:13 +10:00
Basraah
83b62525eb Added support for group leaders to manage groups 2016-12-05 21:49:12 +10:00
Basraah
9eba5607d2 Consolidate member state checking for easier code reuse 2016-12-05 20:42:47 +10:00
Basraah
5fb64d8c06 Switched to use navactive for menu highlighting 2016-12-05 14:35:08 +10:00
Basraah
ed461d9e7a Add blankable fields 2016-12-04 23:41:26 +10:00
Basraah
bf345361b2 Refactor Group extension models into a single OneToOne model
Added group leader field
2016-12-04 23:05:04 +10:00
Basraah
648753a68a Prevent users requesting or leaving non-joinable groups
I have not prevented users joining hidden groups however, as
there may be some use cases where the direct link is provided
for users to request access to the group.

Also prevent users generating leave requests for groups they
are not a member of.
2016-12-04 13:02:25 +10:00
Basraah
42def30c91 Include requestable groups other than open 2016-12-04 09:21:43 +10:00
Basraah
25dadf81f6 Add open/hidden group membership display and remove 2016-12-03 21:45:55 +10:00
Basraah
2dd75b5100 Switched styling to warning, added tooltip 2016-11-24 10:56:36 +10:00
Basraah
eb6d7e9d9a Improve layout on small display resolutions 2016-11-23 22:35:08 +10:00
Basraah
1c4d2533b1 Replace API key management w/ ported profile page
Shows characters on each API key, main character, API key
status and the user groups.
2016-11-23 21:17:44 +10:00
478 changed files with 21807 additions and 24569 deletions

32
.coveragerc Normal file
View File

@@ -0,0 +1,32 @@
[run]
branch = True
source =
alliance_auth
authentication
corputils
eveonline
fleetactivitytracking
fleetup
groupmanagement
hrapplications
notifications
optimer
permissions_tool
services
srp
timerboard
omit =
*/migrations/*
*/example/*
[report]
exclude_lines =
if self.debug:
pragma: no cover
raise NotImplementedError
if __name__ == .__main__.:
def __repr__
raise AssertionError
ignore_errors = True

22
.idea/allianceauth.iml generated
View File

@@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="FacetManager">
<facet type="django" name="Django">
<configuration>
<option name="rootFolder" value="$MODULE_DIR$" />
<option name="settingsModule" value="alliance_auth/settings.py.example" />
<option name="manageScript" value="manage.py" />
<option name="environment" value="&lt;map/&gt;" />
<option name="commandsToSkip" value="" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 2.7.11 virtualenv at ~/1.6" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Django" />
</component>
</module>

12
.idea/dataSources.ids generated
View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<component name="dataSourceStorage">
<data-source source="LOCAL" name="Django default" uuid="3eb61453-647a-4832-8320-f3561f039abc">
<database-info product="" version="" jdbc-version="" driver-name="" driver-version=""/>
</data-source>
<data-source source="LOCAL" name="Django phpbb3" uuid="2de247c2-1951-4e74-8276-6a1c89c396fa">
<database-info product="" version="" jdbc-version="" driver-name="" driver-version=""/>
</data-source>
<data-source source="LOCAL" name="Django mumble" uuid="9963e5ca-7f2f-4dd3-9175-bc7102dfd48c">
<database-info product="" version="" jdbc-version="" driver-name="" driver-version=""/>
</data-source>
</component>

20
.idea/dataSources.xml generated
View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="Django default" uuid="3eb61453-647a-4832-8320-f3561f039abc">
<driver-ref>mysql</driver-ref>
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://127.0.0.1:3306/alliance_auth</jdbc-url>
</data-source>
<data-source source="LOCAL" name="Django phpbb3" uuid="2de247c2-1951-4e74-8276-6a1c89c396fa">
<driver-ref>mysql</driver-ref>
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://127.0.0.1:3306/alliance_forum</jdbc-url>
</data-source>
<data-source source="LOCAL" name="Django mumble" uuid="9963e5ca-7f2f-4dd3-9175-bc7102dfd48c">
<driver-ref>mysql</driver-ref>
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mysql://127.0.0.1:3306/alliance_mumble</jdbc-url>
</data-source>
</component>
</project>

5
.idea/encodings.xml generated
View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
</project>

View File

@@ -1,11 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0" is_locked="false">
<option name="myName" value="Project Default" />
<option name="myLocal" value="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

View File

@@ -1,7 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 2.7.11 virtualenv at ~/1.6" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="1" />
</component>
</project>

9
.idea/modules.xml generated
View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/allianceauth.iml" filepath="$PROJECT_DIR$/.idea/allianceauth.iml" />
</modules>
</component>
</project>

View File

@@ -1,5 +0,0 @@
<component name="DependencyValidationManager">
<state>
<option name="SKIP_IMPORT_STATEMENTS" value="false" />
</state>
</component>

7
.idea/vcs.xml generated
View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

14
.travis.yml Normal file
View File

@@ -0,0 +1,14 @@
language: python
python:
- "2.7"
- "3.5"
# command to install dependencies
install:
- pip install requests
- pip install -r requirements.txt
- pip install -r testing-requirements.txt
# command to run tests
script: coverage run runtests.py
cache: pip
after_success:
coveralls

View File

@@ -2,74 +2,31 @@ Alliance Auth
============ ============
[![Join the chat at https://gitter.im/R4stl1n/allianceauth](https://badges.gitter.im/R4stl1n/allianceauth.svg)](https://gitter.im/R4stl1n/allianceauth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the chat at https://gitter.im/R4stl1n/allianceauth](https://badges.gitter.im/R4stl1n/allianceauth.svg)](https://gitter.im/R4stl1n/allianceauth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Documentation Status](https://readthedocs.org/projects/allianceauth/badge/?version=latest)](http://allianceauth.readthedocs.io/?badge=latest)
[![Build Status](https://travis-ci.org/allianceauth/allianceauth.svg?branch=master)](https://travis-ci.org/allianceauth/allianceauth)
[![Coverage Status](https://coveralls.io/repos/github/allianceauth/allianceauth/badge.svg?branch=master)](https://coveralls.io/github/allianceauth/allianceauth?branch=master)
Alliance service auth to help large scale alliances manage services.
Built for "The 99 Percent" open for anyone to use
[Documentation and Setup Guides](https://github.com/R4stl1n/allianceauth/wiki) EVE service auth to help corps, alliances, and coalitions manage services.
Built for "The 99 Percent" open for anyone to use.
[Project Website](http://r4stl1n.github.io/allianceauth/) [Read the docs here.](http://allianceauth.rtfd.io)
[Old Dev Setup Guide] (http://r4stl1n.github.io/allianceauth/quicksetup.html)
[Old Production Setup Guide] (http://r4stl1n.github.io/allianceauth/fullsetup.html)
Join us in-game in the channel allianceauth for help and feature requests.
Special Thanks:
Thanks to Nikdoof, without his old auth implementation this project wouldn't be as far as it is now.
Note:
Please keep your admin account and normal accounts separate. If you are the admin only use
the admin account for admin stuff do not attempt to use it for your personal services.
Create a new normal account for this or things will break.
Requirements:
# Django Stuff #
django 1.10.1
django-bootstrap-form
django-celery
# Python Stuff #
python-mysql-connector
python-mysqld
python-passlib
python-evelink
python-openfire
python-xmpp
python-dnspython
# Needed Apps #
Rabbitmq server
Startup Instructions:
./bootstrap.sh (Sudo if needed)
./startup.sh
./shutdown.sh
Vagrant Instructions:
Copy the scripts to the root directory before running
Special Permissions In Admin: Special Permissions In Admin:
auth | user | group_management ( Access to add members to groups within the alliance ) auth | user | group_management ( Access to add members to groups within the alliance )
auth | user | jabber_broadcast ( Access to broadcast a message over jabber to own groups) auth | user | jabber_broadcast ( Access to broadcast a message over jabber to own groups )
auth | user | jabber_broadcast_all ( Can choose from all groups and the 'all' option when broadcasting) auth | user | jabber_broadcast_all ( Can choose from all groups and the 'all' option when broadcasting )
auth | user | corp_apis ( View APIs, and jackKnife, of all members in user's corp. ) auth | user | corp_apis ( View APIs, and jackKnife, of all members in user's corp. )
auth | user | alliance_apis ( View APIs, and jackKnife, of all member in user's alliance member corps. ) auth | user | alliance_apis ( View APIs, and jackKnife, of all member in user's alliance member corps. )
auth | user | timer_management ( Access to create and remove timers) auth | user | timer_management ( Access to create and remove timers )
auth | user | timer_view ( Access to timerboard to view timers) auth | user | timer_view ( Access to timerboard to view timers )
auth | user | srp_management ( Allows for an individual to create and remove srp fleets and fleet data) auth | user | srp_management ( Allows for an individual to create and remove srp fleets and fleet data )
auth | user | sigtracker_management ( Allows for an individual to create and remove signitures) auth | user | sigtracker_management ( Allows for an individual to create and remove signitures )
auth | user | sigtracker_view ( Allows for an individual view signitures) auth | user | sigtracker_view ( Allows for an individual view signitures )
auth | user | optimer_management ( Allows for an individual to create and remove fleet operations) auth | user | optimer_management ( Allows for an individual to create and remove fleet operations )
auth | user | optimer_view ( Allows for an individual view fleet operations) auth | user | optimer_view ( Allows for an individual view fleet operations )
auth | user | logging_notifications ( Generate notifications from logging) auth | user | logging_notifications ( Generate notifications from logging )
auth | user | human_resources ( View applications to user's corp ) auth | user | human_resources ( View applications to user's corp )
hrapplications | application | delete_application ( Can delete applications ) hrapplications | application | delete_application ( Can delete applications )
@@ -78,14 +35,21 @@ Special Permissions In Admin:
hrapplications | application | view_apis ( Can see applicant's API keys ) hrapplications | application | view_apis ( Can see applicant's API keys )
hrapplications | applicationcomment | add_applicationcomment ( Can comment on applications ) hrapplications | applicationcomment | add_applicationcomment ( Can comment on applications )
Active Developers Vagrant Instructions:
Copy the scripts to the root directory before running
Active Developers:
Adarnof Adarnof
Kaezon Rio basraah
Mr McClain
Beta Testers/ Bug Fixers: Beta Testers/ Bug Fixers:
TrentBartlem ( Testing and Bug Fixes) TrentBartlem ( Testing and Bug Fixes )
IskFiend ( Bug Fixes and Server Configuration ) IskFiend ( Bug Fixes and Server Configuration )
Mr McClain (Bug Fixes and server configuration) Mr McClain (Bug Fixes and server configuration )
Special Thanks:
Thanks to Nikdoof, without his old auth implementation this project wouldn't be as far as it is now.

View File

@@ -1 +1,8 @@
from __future__ import unicode_literals from __future__ import absolute_import, unicode_literals
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celeryapp import app as celery_app # noqa
__version__ = '1.15.8'
NAME = 'Alliance Auth v%s' % __version__

View File

@@ -0,0 +1,17 @@
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'alliance_auth.settings')
from django.conf import settings # noqa
app = Celery('alliance_auth')
# Using a string here means the worker don't have to serialize
# the configuration object to child processes.
app.config_from_object('django.conf:settings')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

127
alliance_auth/hooks.py Normal file
View File

@@ -0,0 +1,127 @@
"""
Copyright (c) 2014 Torchbox Ltd and individual contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of Torchbox nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Based on https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailcore/hooks.py
"""
from __future__ import unicode_literals
from importlib import import_module
from django.apps import apps
from django.utils.module_loading import module_has_submodule
import logging
logger = logging.getLogger(__name__)
_hooks = {} # Dict of Name: Fn's of registered hooks
_all_hooks_registered = False # If all hooks have been searched for and registered yet
def register(name, fn=None):
"""
Decorator to register a function as a hook
Register hook for ``hook_name``. Can be used as a decorator::
@register('hook_name')
def my_hook(...):
pass
or as a function call::
def my_hook(...):
pass
register('hook_name', my_hook)
:param name: str Name of the hook/callback to register it as
:param fn: function to register in the hook/callback
:return: function Decorator if applied as a decorator
"""
def _hook_add(func):
if name not in _hooks:
logger.debug("Creating new hook %s" % name)
_hooks[name] = []
logger.debug('Registering hook %s for function %s' % (name, fn))
_hooks[name].append(func)
if fn is None:
# Behave like a decorator
def decorator(func):
_hook_add(func)
return func
return decorator
else:
# Behave like a function, just register hook
_hook_add(fn)
def get_app_modules():
"""
Get all the modules of the django app
:return: name, module tuple
"""
for app in apps.get_app_configs():
yield app.name, app.module
def get_app_submodules(module_name):
"""
Get a specific sub module of the app
:param module_name: module name to get
:return: name, module tuple
"""
for name, module in get_app_modules():
if module_has_submodule(module, module_name):
yield name, import_module('{0}.{1}'.format(name, module_name))
def register_all_hooks():
"""
Register all hooks found in 'auth_hooks' sub modules
:return:
"""
global _all_hooks_registered
if not _all_hooks_registered:
logger.debug("Searching for hooks")
hooks = list(get_app_submodules('auth_hooks'))
logger.debug("Got %s hooks" % len(hooks))
_all_hooks_registered = True
def get_hooks(name):
"""
Get all the hook functions for the given hook name
:param name: str name of the hook to get the functions for
:return: list of hook functions
"""
register_all_hooks()
return _hooks.get(name, [])

View File

@@ -1,3 +1,4 @@
# -*- coding: UTF-8 -*-
""" """
Django settings for alliance_auth project. Django settings for alliance_auth project.
@@ -12,15 +13,12 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
import os import os
import djcelery
from django.contrib import messages from django.contrib import messages
from celery.schedules import crontab
djcelery.setup_loader()
# Celery configuration # Celery configuration
BROKER_URL = 'redis://localhost:6379/0' BROKER_URL = 'redis://localhost:6379/0'
CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -48,7 +46,7 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.humanize', 'django.contrib.humanize',
'djcelery', 'django_celery_beat',
'bootstrapform', 'bootstrapform',
'authentication', 'authentication',
'services', 'services',
@@ -60,9 +58,27 @@ INSTALLED_APPS = [
'optimer', 'optimer',
'corputils', 'corputils',
'fleetactivitytracking', 'fleetactivitytracking',
'fleetup',
'notifications', 'notifications',
'eve_sso', 'esi',
'permissions_tool',
'geelweb.django.navhelper', 'geelweb.django.navhelper',
'bootstrap_pagination',
'captcha',
# Services
'services.modules.mumble',
'services.modules.discord',
'services.modules.discourse',
'services.modules.ipboard',
'services.modules.ips4',
'services.modules.market',
'services.modules.openfire',
'services.modules.seat',
'services.modules.smf',
'services.modules.phpbb3',
'services.modules.xenforo',
'services.modules.teamspeak3',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -110,7 +126,7 @@ TEMPLATES = [
'notifications.context_processors.user_notification_count', 'notifications.context_processors.user_notification_count',
'authentication.context_processors.states', 'authentication.context_processors.states',
'authentication.context_processors.membership_state', 'authentication.context_processors.membership_state',
'authentication.context_processors.sso', 'groupmanagement.context_processors.can_manage_groups',
], ],
}, },
}, },
@@ -133,6 +149,15 @@ DATABASES = {
}, },
} }
# If you have run the authentication.0013_service_modules migration
# you will need to set this to True in order to install service modules
# which were involved in that migration after it has been run.
# If you are on a fresh install with no existing database you can safely
# set this to True
# If you have not run the authentication.0013_service_modules migration
# leave this set to False.
SERVICES_MIGRATED = False
# Password validation # Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
@@ -184,20 +209,49 @@ MESSAGE_TAGS = {
messages.ERROR: 'danger' messages.ERROR: 'danger'
} }
CACHES = {
"default": {
"BACKEND": "redis_cache.RedisCache",
"LOCATION": "localhost:6379",
"OPTIONS": {
"DB": 1,
}
}
}
# Google Recaptcha
CAPTCHA_ENABLED = False
RECAPTCHA_PUBLIC_KEY = 'MyRecaptchaKey'
RECAPTCHA_PRIVATE_KEY = 'MyRecaptchaPrivateKey'
NOCAPTCHA = True
##################################################### #####################################################
## ##
## Auth configuration starts here ## Auth configuration starts here
## ##
##################################################### #####################################################
###########################
# ALLIANCE / CORP TOGGLE #########################
########################### # CELERY SCHEDULED TASKS
# Specifies to run membership checks against corp or alliance #########################
# Set to FALSE for alliance CELERYBEAT_SCHEDULE = {
# Set to TRUE for corp 'run_api_refresh': {
########################### 'task': 'eveonline.tasks.run_api_refresh',
IS_CORP = 'True' == os.environ.get('AA_IS_CORP', 'True') 'schedule': crontab(minute=0, hour="*/3"),
},
'run_corp_update': {
'task': 'eveonline.tasks.run_corp_update',
'schedule': crontab(minute=0, hour="*/2"),
},
'update_all_corpstats': {
'task': 'corputils.tasks.update_all_corpstats',
'schedule': crontab(minute=0, hour="*/6"),
},
}
################# #################
# EMAIL SETTINGS # EMAIL SETTINGS
@@ -209,7 +263,7 @@ IS_CORP = 'True' == os.environ.get('AA_IS_CORP', 'True')
# EMAIL_HOST_PASSWORD - Email Password # EMAIL_HOST_PASSWORD - Email Password
# EMAIL_USE_TLS - Set to use TLS encryption # EMAIL_USE_TLS - Set to use TLS encryption
################# #################
DOMAIN = os.environ.get('AA_DOMAIN', 'https://yourdomain.com') DOMAIN = os.environ.get('AA_DOMAIN', 'https://example.com')
EMAIL_HOST = os.environ.get('AA_EMAIL_HOST', 'smtp.gmail.com') EMAIL_HOST = os.environ.get('AA_EMAIL_HOST', 'smtp.gmail.com')
EMAIL_PORT = int(os.environ.get('AA_EMAIL_PORT', '587')) EMAIL_PORT = int(os.environ.get('AA_EMAIL_PORT', '587'))
EMAIL_HOST_USER = os.environ.get('AA_EMAIL_HOST_USER', '') EMAIL_HOST_USER = os.environ.get('AA_EMAIL_HOST_USER', '')
@@ -222,24 +276,23 @@ EMAIL_USE_TLS = 'True' == os.environ.get('AA_EMAIL_USE_TLS', 'True')
# KILLBOARD_URL - URL for your killboard. Blank to hide link # KILLBOARD_URL - URL for your killboard. Blank to hide link
# MEDIA_URL - URL for your media page (youtube etc). Blank to hide link # MEDIA_URL - URL for your media page (youtube etc). Blank to hide link
# FORUM_URL - URL for your forums. Blank to hide link # FORUM_URL - URL for your forums. Blank to hide link
# SMF_URL - URL for your SMF forums. # SITE_NAME - Name of the auth site.
#################### ####################
KILLBOARD_URL = os.environ.get('AA_KILLBOARD_URL', '') KILLBOARD_URL = os.environ.get('AA_KILLBOARD_URL', '')
EXTERNAL_MEDIA_URL = os.environ.get('AA_EXTERNAL_MEDIA_URL', '') EXTERNAL_MEDIA_URL = os.environ.get('AA_EXTERNAL_MEDIA_URL', '')
FORUM_URL = os.environ.get('AA_FORUM_URL', '') FORUM_URL = os.environ.get('AA_FORUM_URL', '')
SITE_NAME = os.environ.get('AA_SITE_NAME', 'Alliance Auth')
################### ###################
# SSO Settings # SSO Settings
################### ###################
# Optional SSO.
# Get client ID and client secret from registering an app at # Get client ID and client secret from registering an app at
# https://developers.eveonline.com/ # https://developers.eveonline.com/
# Callback URL should be http://mydomain.com/sso/callback # Callback URL should be https://example.com/sso/callback
# Leave callback blank to hide SSO button on login page
################### ###################
EVE_SSO_CLIENT_ID = os.environ.get('AA_EVE_SSO_CLIENT_ID', '') ESI_SSO_CLIENT_ID = os.environ.get('AA_ESI_SSO_CLIENT_ID', '')
EVE_SSO_CLIENT_SECRET = os.environ.get('AA_EVE_SSO_CLIENT_SECRET', '') ESI_SSO_CLIENT_SECRET = os.environ.get('AA_ESI_SSO_CLIENT_SECRET', '')
EVE_SSO_CALLBACK_URL = os.environ.get('AA_EVE_SSO_CALLBACK_URL', '') ESI_SSO_CALLBACK_URL = os.environ.get('AA_ESI_SSO_CALLBACK_URL', '')
######################### #########################
# Default Group Settings # Default Group Settings
@@ -257,83 +310,32 @@ BLUE_CORP_GROUPS = 'True' == os.environ.get('AA_BLUE_CORP_GROUPS', 'False')
BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'False') BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'False')
######################### #########################
# Alliance Service Setup # Tenant Configuration
######################### #########################
# ENABLE_AUTH_FORUM - Enable forum support in the auth for auth'd members # CORP_IDS - A list of corporation IDs to treat as members.
# ENABLE_AUTH_JABBER - Enable jabber support in the auth for auth'd members # ALLIANCE_IDS - A list of alliance IDs to treat as members.
# ENABLE_AUTH_MUMBLE - Enable mumble support in the auth for auth'd members # Any corps in a specified alliance will be treated as members, so do not include them in CORP_IDS
# ENABLE_AUTH_IPBOARD - Enable IPBoard forum support in the auth for auth'd members
# ENABLE_AUTH_DISCORD - Enable Discord support in the auth for auth'd members
# ENABLE_AUTH_DISCOURSE - Enable Discourse support in the auth for auth'd members
# ENABLE_AUTH_IPS4 - Enable IPS4 support in the auth for auth'd members
# ENABLE_AUTH_SMF - Enable SMF forum support in the auth for auth'd members
# ENABLE_AUTH_MARKET = Enable Alliance Market support in auth for auth'd members
# ENABLE_AUTH_PATHFINDER = Enable Alliance Pathfinder suppor in auth for auth'd members
# ENABLE_AUTH_XENFORO = Enable XenForo forums support in the auth for auth'd members
######################### #########################
ENABLE_AUTH_FORUM = 'True' == os.environ.get('AA_ENABLE_AUTH_FORUM', 'False') CORP_IDS = []
ENABLE_AUTH_JABBER = 'True' == os.environ.get('AA_ENABLE_AUTH_JABBER', 'False') ALLIANCE_IDS = []
ENABLE_AUTH_MUMBLE = 'True' == os.environ.get('AA_ENABLE_AUTH_MUMBLE', 'False')
ENABLE_AUTH_IPBOARD = 'True' == os.environ.get('AA_ENABLE_AUTH_IPBOARD', 'False')
ENABLE_AUTH_TEAMSPEAK3 = 'True' == os.environ.get('AA_ENABLE_AUTH_TEAMSPEAK3', 'False')
ENABLE_AUTH_DISCORD = 'True' == os.environ.get('AA_ENABLE_AUTH_DISCORD', 'False')
ENABLE_AUTH_DISCOURSE = 'True' == os.environ.get('AA_ENABLE_AUTH_DISCOURSE', 'False')
ENABLE_AUTH_IPS4 = 'True' == os.environ.get('AA_ENABLE_AUTH_IPS4', 'False')
ENABLE_AUTH_SMF = 'True' == os.environ.get('AA_ENABLE_AUTH_SMF', 'False')
ENABLE_AUTH_MARKET = 'True' == os.environ.get('AA_ENABLE_AUTH_MARKET', 'False')
ENABLE_AUTH_XENFORO = 'True' == os.environ.get('AA_ENABLE_AUTH_XENFORO', 'False')
#####################
# Blue service Setup
#####################
# BLUE_STANDING - The default lowest standings setting to consider blue
# ENABLE_BLUE_FORUM - Enable forum support in the auth for blues
# ENABLE_BLUE_JABBER - Enable jabber support in the auth for blues
# ENABLE_BLUE_MUMBLE - Enable mumble support in the auth for blues
# ENABLE_BLUE_IPBOARD - Enable IPBoard forum support in the auth for blues
# ENABLE_BLUE_DISCORD - Enable Discord support in the auth for blues
# ENABLE_BLUE_DISCOURSE - Enable Discord support in the auth for blues
# ENABLE_BLUE_IPS4 - Enable IPS4 forum support in the auth for blues
# ENABLE_BLUE_SMF - Enable SMF forum support in the auth for blues
# ENABLE_BLUE_MARKET - Enable Alliance Market in the auth for blues
# ENABLE_BLUE_PATHFINDER = Enable Pathfinder support in the auth for blues
# ENABLE_BLUE_XENFORO = Enable XenForo forum support in the auth for blue
#####################
BLUE_STANDING = float(os.environ.get('AA_BLUE_STANDING', '5.0'))
ENABLE_BLUE_FORUM = 'True' == os.environ.get('AA_ENABLE_BLUE_FORUM', 'False')
ENABLE_BLUE_JABBER = 'True' == os.environ.get('AA_ENABLE_BLUE_JABBER', 'False')
ENABLE_BLUE_MUMBLE = 'True' == os.environ.get('AA_ENABLE_BLUE_MUMBLE', 'False')
ENABLE_BLUE_IPBOARD = 'True' == os.environ.get('AA_ENABLE_BLUE_IPBOARD', 'False')
ENABLE_BLUE_TEAMSPEAK3 = 'True' == os.environ.get('AA_ENABLE_BLUE_TEAMSPEAK3', 'False')
ENABLE_BLUE_DISCORD = 'True' == os.environ.get('AA_ENABLE_BLUE_DISCORD', 'False')
ENABLE_BLUE_DISCOURSE = 'True' == os.environ.get('AA_ENABLE_BLUE_DISCOURSE', 'False')
ENABLE_BLUE_IPS4 = 'True' == os.environ.get('AA_ENABLE_BLUE_IPS4', 'False')
ENABLE_BLUE_SMF = 'True' == os.environ.get('AA_ENABLE_BLUE_SMF', 'False')
ENABLE_BLUE_MARKET = 'True' == os.environ.get('AA_ENABLE_BLUE_MARKET', 'False')
ENABLE_BLUE_XENFORO = 'True' == os.environ.get('AA_ENABLE_BLUE_XENFORO', 'False')
######################### #########################
# Corp Configuration # Standings Configuration
######################### #########################
# If running in alliance mode, the following should be for the executor corp# # Add a corp API key to add blue standings to grant access.
# CORP_ID - Set this to your corp ID (get this from https://zkillboard.com/corporation/#######)
# CORP_NAME - Set this to your Corporation Name
# CORP_API_ID - Set this to the api id for the corp API key # CORP_API_ID - Set this to the api id for the corp API key
# CORP_API_VCODE - Set this to the api vcode for the corp API key # CORP_API_VCODE - Set this to the api vcode for the corp API key
# BLUE_STANDING - The lowest standings value to consider blue
# STANDING_LEVEL - The level of standings to query. Accepted values are 'corp' and 'alliance'.
# BLUE_CORP_IDS - A list of corps to remain blue regardless of in-game standings
# BLUE_ALLIANCE_IDS - A list of alliances to remain blue regardless of in-game standings
######################## ########################
CORP_ID = os.environ.get('AA_CORP_ID', '')
CORP_NAME = os.environ.get('AA_CORP_NAME', '')
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '') CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '') CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
BLUE_STANDING = float(os.environ.get('AA_BLUE_STANDING', '5.0'))
######################### STANDING_LEVEL = os.environ.get('AA_STANDING_LEVEL', 'corp')
# Alliance Configuration BLUE_CORP_IDS = []
######################### BLUE_ALLIANCE_IDS = []
# ALLIANCE_ID - Set this to your Alliance ID (get this from https://zkillboard.com/alliance/#######)
# ALLIANCE_NAME - Set this to your Alliance Name
########################
ALLIANCE_ID = os.environ.get('AA_ALLIANCE_ID', '')
ALLIANCE_NAME = os.environ.get('AA_ALLIANCE_NAME', '')
######################## ########################
# API Configuration # API Configuration
@@ -355,14 +357,29 @@ REJECT_OLD_APIS = 'True' == os.environ.get('AA_REJECT_OLD_APIS', 'False')
REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50) REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50)
API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False') API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
#######################
# EVE Provider Settings
#######################
# EVEONLINE_CHARACTER_PROVIDER - Name of default data source for getting eve character data
# EVEONLINE_CORP_PROVIDER - Name of default data source for getting eve corporation data
# EVEONLINE_ALLIANCE_PROVIDER - Name of default data source for getting eve alliance data
# EVEONLINE_ITEMTYPE_PROVIDER - Name of default data source for getting eve item type data
#
# Available sources are 'esi' and 'xml'. Leaving blank results in the default 'esi' being used.
#######################
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', '')
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', '')
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', '')
EVEONLINE_ITEMTYPE_PROVIDER = os.environ.get('AA_EVEONLINE_ITEMTYPE_PROVIDER', '')
##################### #####################
# Alliance Market # Alliance Market
##################### #####################
MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://yourdomain.com/market') MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://example.com/market')
MARKET_DB = { MARKET_DB = {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': 'alliance_market', 'NAME': 'alliance_market',
'USER': os.environ.get('AA_DB_MARKET_USER', 'allianceserver'), 'USER': os.environ.get('AA_DB_MARKET_USER', 'allianceserver-market'),
'PASSWORD': os.environ.get('AA_DB_MARKET_PASSWORD', 'password'), 'PASSWORD': os.environ.get('AA_DB_MARKET_PASSWORD', 'password'),
'HOST': os.environ.get('AA_DB_MARKET_HOST', '127.0.0.1'), 'HOST': os.environ.get('AA_DB_MARKET_HOST', '127.0.0.1'),
'PORT': os.environ.get('AA_DB_MARKET_PORT', '3306'), 'PORT': os.environ.get('AA_DB_MARKET_PORT', '3306'),
@@ -371,10 +388,16 @@ MARKET_DB = {
##################### #####################
# HR Configuration # HR Configuration
##################### #####################
# JACK_KNIFE_URL - Url for the audit page of API Jack knife # API_KEY_AUDIT_URL - URL for viewing API keys.
# Should seriously replace with your own. # Other URLs are supported. Use string formatting notation {} with parameter names api_id, vcode, pk
# Example URL formats are shown below:
# Old Jacknife: 'https://example.com/jacknife/eveapi/audit.php?usid={api_id}&apik={vcode}'
# New Jacknife: 'https://example.com/jacknife/?usid={api_id}&apik={vcode}'
# SeAT: 'https://seat.example.com/api-key/detail/{api_id}'
# Django Admin: '/admin/eveonline/eveapikeypair/{pk}/change'
# Leave blank for the verification code to be shown in a popup on click.
##################### #####################
JACK_KNIFE_URL = os.environ.get('AA_JACK_KNIFE_URL', 'http://ridetheclown.com/eveapi/audit.php') API_KEY_AUDIT_URL = os.environ.get('AA_API_KEY_AUDIT_URL', '')
##################### #####################
# Forum Configuration # Forum Configuration
@@ -383,14 +406,14 @@ JACK_KNIFE_URL = os.environ.get('AA_JACK_KNIFE_URL', 'http://ridetheclown.com/ev
# IPBOARD_APIKEY - Api key to interact with ipboard # IPBOARD_APIKEY - Api key to interact with ipboard
# IPBOARD_APIMODULE - Module for alliance auth *leave alone* # IPBOARD_APIMODULE - Module for alliance auth *leave alone*
##################### #####################
IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'yourdomain.com/interface/board/index.php') IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'example.com/interface/board/index.php')
IPBOARD_APIKEY = os.environ.get('AA_IPBOARD_APIKEY', 'somekeyhere') IPBOARD_APIKEY = os.environ.get('AA_IPBOARD_APIKEY', 'somekeyhere')
IPBOARD_APIMODULE = 'aa' IPBOARD_APIMODULE = 'aa'
######################## ########################
# XenForo Configuration # XenForo Configuration
######################## ########################
XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'yourdomain.com/api.php') XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'example.com/api.php')
XENFORO_DEFAULT_GROUP = os.environ.get('AA_XENFORO_DEFAULT_GROUP', 0) XENFORO_DEFAULT_GROUP = os.environ.get('AA_XENFORO_DEFAULT_GROUP', 0)
XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey') XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
##################### #####################
@@ -407,10 +430,10 @@ XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
# BROADCAST_USER - Broadcast user JID # BROADCAST_USER - Broadcast user JID
# BROADCAST_USER_PASSWORD - Broadcast user password # BROADCAST_USER_PASSWORD - Broadcast user password
###################### ######################
JABBER_URL = os.environ.get('AA_JABBER_URL', "yourdomain.com") JABBER_URL = os.environ.get('AA_JABBER_URL', "example.com")
JABBER_PORT = int(os.environ.get('AA_JABBER_PORT', '5223')) JABBER_PORT = int(os.environ.get('AA_JABBER_PORT', '5223'))
JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "yourdomain.com") JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "example.com")
OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://yourdomain.com:9090") OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://example.com:9090")
OPENFIRE_SECRET_KEY = os.environ.get('AA_OPENFIRE_SECRET_KEY', "somekey") OPENFIRE_SECRET_KEY = os.environ.get('AA_OPENFIRE_SECRET_KEY', "somekey")
BROADCAST_USER = os.environ.get('AA_BROADCAST_USER', "broadcast@") + JABBER_URL BROADCAST_USER = os.environ.get('AA_BROADCAST_USER', "broadcast@") + JABBER_URL
BROADCAST_USER_PASSWORD = os.environ.get('AA_BROADCAST_USER_PASSWORD', "somepassword") BROADCAST_USER_PASSWORD = os.environ.get('AA_BROADCAST_USER_PASSWORD', "somepassword")
@@ -419,11 +442,10 @@ BROADCAST_SERVICE_NAME = os.environ.get('AA_BROADCAST_SERVICE_NAME', "broadcast"
###################################### ######################################
# Mumble Configuration # Mumble Configuration
###################################### ######################################
# MUMBLE_URL - Mumble server url # MUMBLE_URL - Mumble server host
# MUMBLE_SERVER_ID - Mumble server id # Do not include leading http:// or mumble://
###################################### ######################################
MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "yourdomain.com") MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "example.com")
MUMBLE_SERVER_ID = int(os.environ.get('AA_MUMBLE_SERVER_ID', '1'))
###################################### ######################################
# PHPBB3 Configuration # PHPBB3 Configuration
@@ -431,7 +453,7 @@ MUMBLE_SERVER_ID = int(os.environ.get('AA_MUMBLE_SERVER_ID', '1'))
PHPBB3_DB = { PHPBB3_DB = {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': 'alliance_forum', 'NAME': 'alliance_forum',
'USER': os.environ.get('AA_DB_PHPBB3_USER', 'allianceserver'), 'USER': os.environ.get('AA_DB_PHPBB3_USER', 'allianceserver-phpbb3'),
'PASSWORD': os.environ.get('AA_DB_PHPBB3_PASSWORD', 'password'), 'PASSWORD': os.environ.get('AA_DB_PHPBB3_PASSWORD', 'password'),
'HOST': os.environ.get('AA_DB_PHPBB3_HOST', '127.0.0.1'), 'HOST': os.environ.get('AA_DB_PHPBB3_HOST', '127.0.0.1'),
'PORT': os.environ.get('AA_DB_PHPBB3_PORT', '3306'), 'PORT': os.environ.get('AA_DB_PHPBB3_PORT', '3306'),
@@ -453,14 +475,13 @@ TEAMSPEAK3_SERVER_PORT = int(os.environ.get('AA_TEAMSPEAK3_SERVER_PORT', '10011'
TEAMSPEAK3_SERVERQUERY_USER = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_USER', 'serveradmin') TEAMSPEAK3_SERVERQUERY_USER = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_USER', 'serveradmin')
TEAMSPEAK3_SERVERQUERY_PASSWORD = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_PASSWORD', 'passwordhere') TEAMSPEAK3_SERVERQUERY_PASSWORD = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_PASSWORD', 'passwordhere')
TEAMSPEAK3_VIRTUAL_SERVER = int(os.environ.get('AA_TEAMSPEAK3_VIRTUAL_SERVER', '1')) TEAMSPEAK3_VIRTUAL_SERVER = int(os.environ.get('AA_TEAMSPEAK3_VIRTUAL_SERVER', '1'))
TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'yourdomain.com') TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com')
###################################### ######################################
# Discord Configuration # Discord Configuration
###################################### ######################################
# DISCORD_GUILD_ID - ID of the guild to manage # DISCORD_GUILD_ID - ID of the guild to manage
# DISCORD_BOT_TOKEN - oauth token of the app bot user # DISCORD_BOT_TOKEN - oauth token of the app bot user
# DISCORD_INVITE_CODE - invite code to the server
# DISCORD_APP_ID - oauth app client ID # DISCORD_APP_ID - oauth app client ID
# DISCORD_APP_SECRET - oauth app secret # DISCORD_APP_SECRET - oauth app secret
# DISCORD_CALLBACK_URL - oauth callback url # DISCORD_CALLBACK_URL - oauth callback url
@@ -468,10 +489,9 @@ TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'yourdomain.c
###################################### ######################################
DISCORD_GUILD_ID = os.environ.get('AA_DISCORD_GUILD_ID', '') DISCORD_GUILD_ID = os.environ.get('AA_DISCORD_GUILD_ID', '')
DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', '') DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', '')
DISCORD_INVITE_CODE = os.environ.get('AA_DISCORD_INVITE_CODE', '')
DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', '') DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', '')
DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', '') DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', '')
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://mydomain.com/discord_callback') DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://example.com/discord/callback')
DISCORD_SYNC_NAMES = 'True' == os.environ.get('AA_DISCORD_SYNC_NAMES', 'False') DISCORD_SYNC_NAMES = 'True' == os.environ.get('AA_DISCORD_SYNC_NAMES', 'False')
###################################### ######################################
@@ -493,26 +513,34 @@ DISCOURSE_SSO_SECRET = os.environ.get('AA_DISCOURSE_SSO_SECRET', '')
# IPS4_URL - base url of the IPS4 install (no trailing slash) # IPS4_URL - base url of the IPS4 install (no trailing slash)
# IPS4_API_KEY - API key provided by IPS4 # IPS4_API_KEY - API key provided by IPS4
##################################### #####################################
IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://yourdomain.com/ips4') IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://example.com/ips4')
IPS4_API_KEY = os.environ.get('AA_IPS4_API_KEY', '') IPS4_API_KEY = os.environ.get('AA_IPS4_API_KEY', '')
IPS4_DB = { IPS4_DB = {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': 'alliance_ips4', 'NAME': 'alliance_ips4',
'USER': os.environ.get('AA_DB_IPS4_USER', 'allianceserver'), 'USER': os.environ.get('AA_DB_IPS4_USER', 'allianceserver-ips4'),
'PASSWORD': os.environ.get('AA_DB_IPS4_PASSWORD', 'password'), 'PASSWORD': os.environ.get('AA_DB_IPS4_PASSWORD', 'password'),
'HOST': os.environ.get('AA_DB_IPS4_HOST', '127.0.0.1'), 'HOST': os.environ.get('AA_DB_IPS4_HOST', '127.0.0.1'),
'PORT': os.environ.get('AA_DB_IPS4_PORT', '3306'), 'PORT': os.environ.get('AA_DB_IPS4_PORT', '3306'),
} }
#####################################
# SEAT Configuration
#####################################
# SEAT_URL - base url of the seat install (no trailing slash)
# SEAT_XTOKEN - API key X-Token provided by SeAT
#####################################
SEAT_URL = os.environ.get('AA_SEAT_URL', 'http://example.com/seat')
SEAT_XTOKEN = os.environ.get('AA_SEAT_XTOKEN', '')
###################################### ######################################
# SMF Configuration # SMF Configuration
###################################### ######################################
SMF_URL = os.environ.get('AA_SMF_URL', '') SMF_URL = os.environ.get('AA_SMF_URL', 'https://example.com')
SMF_DB = { SMF_DB = {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',
'NAME': 'alliance_smf', 'NAME': 'alliance_smf',
'USER': os.environ.get('AA_DB_SMF_USER', 'allianceserver'), 'USER': os.environ.get('AA_DB_SMF_USER', 'allianceserver-smf'),
'PASSWORD': os.environ.get('AA_DB_SMF_PASSWORD', 'password'), 'PASSWORD': os.environ.get('AA_DB_SMF_PASSWORD', 'password'),
'HOST': os.environ.get('AA_DB_SMF_HOST', '127.0.0.1'), 'HOST': os.environ.get('AA_DB_SMF_HOST', '127.0.0.1'),
'PORT': os.environ.get('AA_DB_SMF_PORT', '3306'), 'PORT': os.environ.get('AA_DB_SMF_PORT', '3306'),
@@ -633,6 +661,10 @@ LOGGING = {
'handlers': ['log_file', 'console', 'notifications'], 'handlers': ['log_file', 'console', 'notifications'],
'level': 'ERROR', 'level': 'ERROR',
}, },
'fleetup': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'util': { 'util': {
'handlers': ['log_file', 'console', 'notifications'], 'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG', 'level': 'DEBUG',
@@ -645,11 +677,30 @@ LOGGING = {
} }
# Conditionally add databases only if configured # Conditionally add databases only if configured
if ENABLE_AUTH_FORUM or ENABLE_BLUE_FORUM: if 'services.modules.phpbb3' in INSTALLED_APPS:
DATABASES['phpbb3'] = PHPBB3_DB DATABASES['phpbb3'] = PHPBB3_DB
if ENABLE_AUTH_SMF or ENABLE_BLUE_SMF: if 'services.modules.smf' in INSTALLED_APPS:
DATABASES['smf'] = SMF_DB DATABASES['smf'] = SMF_DB
if ENABLE_AUTH_MARKET or ENABLE_BLUE_MARKET: if 'services.modules.market' in INSTALLED_APPS:
DATABASES['market'] = MARKET_DB DATABASES['market'] = MARKET_DB
if ENABLE_AUTH_IPS4 or ENABLE_BLUE_IPS4: if 'services.modules.ips4' in INSTALLED_APPS:
DATABASES['ips4'] = IPS4_DB DATABASES['ips4'] = IPS4_DB
# Ensure corp/alliance IDs are expected types
STR_CORP_IDS = [str(id) for id in CORP_IDS]
STR_ALLIANCE_IDS = [str(id) for id in ALLIANCE_IDS]
STR_BLUE_CORP_IDS = [str(id) for id in BLUE_CORP_IDS]
STR_BLUE_ALLIANCE_IDS = [str(id) for id in BLUE_ALLIANCE_IDS]
# Conditionally add periodic tasks for services if installed
if 'services.modules.teamspeak3' in INSTALLED_APPS:
CELERYBEAT_SCHEDULE['run_ts3_group_update'] = {
'task': 'services.modules.teamspeak3.tasks.run_ts3_group_update',
'schedule': crontab(minute='*/30'),
}
if 'services.modules.seat' in INSTALLED_APPS:
CELERYBEAT_SCHEDULE['run_seat_api_sync'] = {
'task': 'services.modules.seat.tasks.run_api_sync',
'schedule': crontab(minute='*/30'),
}

0
fleetup/tests.py → alliance_auth/tests/__init__.py Executable file → Normal file
View File

View File

@@ -0,0 +1,98 @@
from __future__ import unicode_literals
from django.db.models.signals import m2m_changed, pre_save
from django.contrib.auth.models import User, Group, Permission
from services.signals import m2m_changed_user_groups, pre_save_user
from services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions
from authentication.signals import pre_save_auth_state
from authentication.tasks import make_member, make_blue
from authentication.models import AuthServicesInfo
from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
from eveonline.models import EveCharacter
class AuthUtils:
def __init__(self):
pass
@staticmethod
def _create_user(username):
return User.objects.create(username=username)
@classmethod
def create_user(cls, username, disconnect_signals=False):
if disconnect_signals:
cls.disconnect_signals()
user = cls._create_user(username)
user.authservicesinfo.state = NONE_STATE
user.authservicesinfo.save()
if disconnect_signals:
cls.connect_signals()
return user
@classmethod
def create_member(cls, username):
cls.disconnect_signals()
user = cls._create_user(username)
user.authservicesinfo.state = MEMBER_STATE
user.authservicesinfo.save()
make_member(user.authservicesinfo)
cls.connect_signals()
return user
@classmethod
def create_blue(cls, username):
cls.disconnect_signals()
user = cls._create_user(username)
user.authservicesinfo.state = BLUE_STATE
user.authservicesinfo.save()
make_blue(user.authservicesinfo)
cls.connect_signals()
return user
@classmethod
def disconnect_signals(cls):
m2m_changed.disconnect(m2m_changed_user_groups, sender=User.groups.through)
m2m_changed.disconnect(m2m_changed_group_permissions, sender=Group.permissions.through)
m2m_changed.disconnect(m2m_changed_user_permissions, sender=User.user_permissions.through)
pre_save.disconnect(pre_save_user, sender=User)
pre_save.disconnect(pre_save_auth_state, sender=AuthServicesInfo)
@classmethod
def connect_signals(cls):
m2m_changed.connect(m2m_changed_user_groups, sender=User.groups.through)
m2m_changed.connect(m2m_changed_group_permissions, sender=Group.permissions.through)
m2m_changed.connect(m2m_changed_user_permissions, sender=User.user_permissions.through)
pre_save.connect(pre_save_user, sender=User)
pre_save.connect(pre_save_auth_state, sender=AuthServicesInfo)
@classmethod
def add_main_character(cls, user, name, character_id, corp_id='', corp_name='', corp_ticker='', alliance_id='',
alliance_name=''):
EveCharacter.objects.create(
character_id=character_id,
character_name=name,
corporation_id=corp_id,
corporation_name=corp_name,
corporation_ticker=corp_ticker,
alliance_id=alliance_id,
alliance_name=alliance_name,
api_id='1234',
user=user
)
AuthServicesInfo.objects.update_or_create(user=user, defaults={'main_char_id': character_id})
@classmethod
def add_permissions_to_groups(cls, perms, groups, disconnect_signals=True):
if disconnect_signals:
cls.disconnect_signals()
for group in groups:
for perm in perms:
group.permissions.add(perm)
if disconnect_signals:
cls.connect_signals()

View File

@@ -0,0 +1,556 @@
"""
Alliance Auth Test Suite Django settings.
"""
import os
from django.contrib import messages
import alliance_auth
# Use nose to run all tests
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
#'--with-coverage',
#'--cover-package=',
#'--exe', # If your tests need this to be found/run, check they py files are not chmodded +x
]
# Celery configuration
CELERY_ALWAYS_EAGER = True # Forces celery to run locally for testing
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(alliance_auth.__file__)))
SECRET_KEY = 'testing only'
DEBUG = True
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django_celery_beat',
'bootstrapform',
'authentication',
'services',
'eveonline',
'groupmanagement',
'hrapplications',
'timerboard',
'srp',
'optimer',
'corputils',
'fleetactivitytracking',
'fleetup',
'notifications',
'esi',
'permissions_tool',
'geelweb.django.navhelper',
'bootstrap_pagination',
'services.modules.mumble',
'services.modules.discord',
'services.modules.discourse',
'services.modules.ipboard',
'services.modules.ips4',
'services.modules.market',
'services.modules.openfire',
'services.modules.seat',
'services.modules.smf',
'services.modules.phpbb3',
'services.modules.xenforo',
'services.modules.teamspeak3',
'django_nose',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
]
ROOT_URLCONF = 'alliance_auth.urls'
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale/'),
)
ugettext = lambda s: s
LANGUAGES = (
('en', ugettext('English')),
('de', ugettext('German')),
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'customization/templates'),
os.path.join(BASE_DIR, 'stock/templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'services.context_processors.auth_settings',
'notifications.context_processors.user_notification_count',
'authentication.context_processors.states',
'authentication.context_processors.membership_state',
'groupmanagement.context_processors.can_manage_groups',
],
},
},
]
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'alliance_auth',
'USER': os.environ.get('AA_DB_DEFAULT_USER', None),
'PASSWORD': os.environ.get('AA_DB_DEFAULT_PASSWORD', None),
'HOST': os.environ.get('AA_DB_DEFAULT_HOST', None)
},
}
LOGIN_URL = 'auth_login_user'
SUPERUSER_STATE_BYPASS = 'True' == os.environ.get('AA_SUPERUSER_STATE_BYPASS', 'True')
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
LANGUAGE_CODE = os.environ.get('AA_LANGUAGE_CODE', 'en')
TIME_ZONE = os.environ.get('AA_TIME_ZONE', 'UTC')
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "customization/static"),
os.path.join(BASE_DIR, "stock/static"),
)
# Bootstrap messaging css workaround
MESSAGE_TAGS = {
messages.ERROR: 'danger'
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
#####################################################
##
## Auth configuration starts here
##
#####################################################
###########################
# ALLIANCE / CORP TOGGLE
###########################
# Specifies to run membership checks against corp or alliance
# Set to FALSE for alliance
# Set to TRUE for corp
###########################
IS_CORP = 'True' == os.environ.get('AA_IS_CORP', 'True')
#################
# EMAIL SETTINGS
#################
# DOMAIN - The alliance auth domain_url
# EMAIL_HOST - SMTP Server URL
# EMAIL_PORT - SMTP Server PORT
# EMAIL_HOST_USER - Email Username (for gmail, the entire address)
# EMAIL_HOST_PASSWORD - Email Password
# EMAIL_USE_TLS - Set to use TLS encryption
#################
DOMAIN = os.environ.get('AA_DOMAIN', 'https://example.com')
EMAIL_HOST = os.environ.get('AA_EMAIL_HOST', 'smtp.example.com')
EMAIL_PORT = int(os.environ.get('AA_EMAIL_PORT', '587'))
EMAIL_HOST_USER = os.environ.get('AA_EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('AA_EMAIL_HOST_PASSWORD', '')
EMAIL_USE_TLS = 'True' == os.environ.get('AA_EMAIL_USE_TLS', 'True')
####################
# Front Page Links
####################
# KILLBOARD_URL - URL for your killboard. Blank to hide link
# MEDIA_URL - URL for your media page (youtube etc). Blank to hide link
# FORUM_URL - URL for your forums. Blank to hide link
# SITE_NAME - Name of the auth site.
####################
KILLBOARD_URL = os.environ.get('AA_KILLBOARD_URL', '')
EXTERNAL_MEDIA_URL = os.environ.get('AA_EXTERNAL_MEDIA_URL', '')
FORUM_URL = os.environ.get('AA_FORUM_URL', '')
SITE_NAME = os.environ.get('AA_SITE_NAME', 'Test Alliance Auth')
###################
# SSO Settings
###################
# Optional SSO.
# Get client ID and client secret from registering an app at
# https://developers.eveonline.com/
# Callback URL should be http://mydomain.com/sso/callback
# Leave callback blank to hide SSO button on login page
###################
ESI_SSO_CLIENT_ID = os.environ.get('AA_ESI_SSO_CLIENT_ID', '')
ESI_SSO_CLIENT_SECRET = os.environ.get('AA_ESI_SSO_CLIENT_SECRET', '')
ESI_SSO_CALLBACK_URL = os.environ.get('AA_ESI_SSO_CALLBACK_URL', '')
#########################
# Default Group Settings
#########################
# DEFAULT_AUTH_GROUP - Default group members are put in
# DEFAULT_BLUE_GROUP - Default group for blue members
# MEMBER_CORP_GROUPS - Assign members to a group representing their main corp
# BLUE_CORP_GROUPS - Assign blues to a group representing their main corp
#########################
DEFAULT_AUTH_GROUP = os.environ.get('AA_DEFAULT_ALLIANCE_GROUP', 'Member')
DEFAULT_BLUE_GROUP = os.environ.get('AA_DEFAULT_BLUE_GROUP', 'Blue')
MEMBER_CORP_GROUPS = 'True' == os.environ.get('AA_MEMBER_CORP_GROUPS', 'True')
MEMBER_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_MEMBER_ALLIANCE_GROUPS', 'False')
BLUE_CORP_GROUPS = 'True' == os.environ.get('AA_BLUE_CORP_GROUPS', 'False')
BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'False')
#########################
# Corp Configuration
#########################
# If running in alliance mode, the following should be for the executor corp#
# CORP_ID - Set this to your corp ID (get this from https://zkillboard.com/corporation/#######)
# CORP_NAME - Set this to your Corporation Name
# CORP_API_ID - Set this to the api id for the corp API key
# CORP_API_VCODE - Set this to the api vcode for the corp API key
########################
CORP_ID = os.environ.get('AA_CORP_ID', '1234')
CORP_NAME = os.environ.get('AA_CORP_NAME', 'Alliance Auth Test Corp')
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
#########################
# Alliance Configuration
#########################
# ALLIANCE_ID - Set this to your Alliance ID (get this from https://zkillboard.com/alliance/#######)
# ALLIANCE_NAME - Set this to your Alliance Name
########################
ALLIANCE_ID = os.environ.get('AA_ALLIANCE_ID', '12345')
ALLIANCE_NAME = os.environ.get('AA_ALLIANCE_NAME', 'Alliance Auth Test Alliance')
########################
# API Configuration
########################
# MEMBER_API_MASK - Numeric value of minimum API mask required for members
# MEMBER_API_ACCOUNT - Require API to be for Account and not character restricted
# BLUE_API_MASK - Numeric value of minimum API mask required for blues
# BLUE_API_ACCOUNT - Require API to be for Account and not character restricted
# REJECT_OLD_APIS - Require each submitted API be newer than the latest submitted API
# REJECT_OLD_APIS_MARGIN - Margin from latest submitted API ID within which a newly submitted API is still accepted
# API_SSO_VALIDATION - Require users to prove ownership of newly entered API keys via SSO
# Requires SSO to be configured.
#######################
MEMBER_API_MASK = os.environ.get('AA_MEMBER_API_MASK', 268435455)
MEMBER_API_ACCOUNT = 'True' == os.environ.get('AA_MEMBER_API_ACCOUNT', 'True')
BLUE_API_MASK = os.environ.get('AA_BLUE_API_MASK', 8388608)
BLUE_API_ACCOUNT = 'True' == os.environ.get('AA_BLUE_API_ACCOUNT', 'True')
REJECT_OLD_APIS = 'True' == os.environ.get('AA_REJECT_OLD_APIS', 'False')
REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50)
API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
#######################
# EVE Provider Settings
#######################
# EVEONLINE_CHARACTER_PROVIDER - Name of default data source for getting eve character data
# EVEONLINE_CORP_PROVIDER - Name of default data source for getting eve corporation data
# EVEONLINE_ALLIANCE_PROVIDER - Name of default data source for getting eve alliance data
# EVEONLINE_ITEMTYPE_PROVIDER - Name of default data source for getting eve item type data
#
# Available sources are 'esi' and 'xml'. Leaving blank results in the default 'esi' being used.
#######################
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', 'xml')
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', 'xml')
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', 'xml')
EVEONLINE_ITEMTYPE_PROVIDER = os.environ.get('AA_EVEONLINE_ITEMTYPE_PROVIDER', 'xml')
#####################
# Alliance Market
#####################
MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://yourdomain.com/market')
#####################
# HR Configuration
#####################
# JACK_KNIFE_URL - Url for the audit page of API Jack knife
# Should seriously replace with your own.
#####################
JACK_KNIFE_URL = os.environ.get('AA_JACK_KNIFE_URL', 'http://example.com/eveapi/audit.php')
#####################
# Forum Configuration
#####################
# IPBOARD_ENDPOINT - Api endpoint if using ipboard
# IPBOARD_APIKEY - Api key to interact with ipboard
# IPBOARD_APIMODULE - Module for alliance auth *leave alone*
#####################
IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'example.com/interface/board/index.php')
IPBOARD_APIKEY = os.environ.get('AA_IPBOARD_APIKEY', 'somekeyhere')
IPBOARD_APIMODULE = 'aa'
########################
# XenForo Configuration
########################
XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'example.com/api.php')
XENFORO_DEFAULT_GROUP = os.environ.get('AA_XENFORO_DEFAULT_GROUP', 0)
XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
#####################
######################
# Jabber Configuration
######################
# JABBER_URL - Jabber address url
# JABBER_PORT - Jabber service portal
# JABBER_SERVER - Jabber server url
# OPENFIRE_ADDRESS - Address of the openfire admin console including port
# Please use http with 9090 or https with 9091
# OPENFIRE_SECRET_KEY - Openfire REST API secret key
# BROADCAST_USER - Broadcast user JID
# BROADCAST_USER_PASSWORD - Broadcast user password
######################
JABBER_URL = os.environ.get('AA_JABBER_URL', "example.com")
JABBER_PORT = int(os.environ.get('AA_JABBER_PORT', '5223'))
JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "example.com")
OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://example.com:9090")
OPENFIRE_SECRET_KEY = os.environ.get('AA_OPENFIRE_SECRET_KEY', "somekey")
BROADCAST_USER = os.environ.get('AA_BROADCAST_USER', "broadcast@") + JABBER_URL
BROADCAST_USER_PASSWORD = os.environ.get('AA_BROADCAST_USER_PASSWORD', "somepassword")
BROADCAST_SERVICE_NAME = os.environ.get('AA_BROADCAST_SERVICE_NAME', "broadcast")
######################################
# Mumble Configuration
######################################
# MUMBLE_URL - Mumble server url
# MUMBLE_SERVER_ID - Mumble server id
######################################
MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "example.com")
MUMBLE_SERVER_ID = int(os.environ.get('AA_MUMBLE_SERVER_ID', '1'))
######################################
# PHPBB3 Configuration
######################################
######################################
# Teamspeak3 Configuration
######################################
# TEAMSPEAK3_SERVER_IP - Teamspeak3 server ip
# TEAMSPEAK3_SERVER_PORT - Teamspeak3 server port
# TEAMSPEAK3_SERVERQUERY_USER - Teamspeak3 serverquery username
# TEAMSPEAK3_SERVERQUERY_PASSWORD - Teamspeak3 serverquery password
# TEAMSPEAK3_VIRTUAL_SERVER - Virtual server id
# TEAMSPEAK3_AUTHED_GROUP_ID - Default authed group id
# TEAMSPEAK3_PUBLIC_URL - teamspeak3 public url used for link creation
######################################
TEAMSPEAK3_SERVER_IP = os.environ.get('AA_TEAMSPEAK3_SERVER_IP', '127.0.0.1')
TEAMSPEAK3_SERVER_PORT = int(os.environ.get('AA_TEAMSPEAK3_SERVER_PORT', '10011'))
TEAMSPEAK3_SERVERQUERY_USER = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_USER', 'serveradmin')
TEAMSPEAK3_SERVERQUERY_PASSWORD = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_PASSWORD', 'passwordhere')
TEAMSPEAK3_VIRTUAL_SERVER = int(os.environ.get('AA_TEAMSPEAK3_VIRTUAL_SERVER', '1'))
TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com')
######################################
# Discord Configuration
######################################
# DISCORD_GUILD_ID - ID of the guild to manage
# DISCORD_BOT_TOKEN - oauth token of the app bot user
# DISCORD_INVITE_CODE - invite code to the server
# DISCORD_APP_ID - oauth app client ID
# DISCORD_APP_SECRET - oauth app secret
# DISCORD_CALLBACK_URL - oauth callback url
# DISCORD_SYNC_NAMES - enable to force discord nicknames to be set to eve char name (bot needs Manage Nicknames permission)
######################################
DISCORD_GUILD_ID = os.environ.get('AA_DISCORD_GUILD_ID', '0118999')
DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', 'bottoken')
DISCORD_INVITE_CODE = os.environ.get('AA_DISCORD_INVITE_CODE', 'invitecode')
DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', 'appid')
DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', 'secret')
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://example.com/discord/callback')
DISCORD_SYNC_NAMES = 'True' == os.environ.get('AA_DISCORD_SYNC_NAMES', 'False')
######################################
# Discourse Configuration
######################################
# DISCOURSE_URL - Web address of the forums (no trailing slash)
# DISCOURSE_API_USERNAME - API account username
# DISCOURSE_API_KEY - API Key
# DISCOURSE_SSO_SECRET - SSO secret key
######################################
DISCOURSE_URL = os.environ.get('AA_DISCOURSE_URL', 'https://example.com')
DISCOURSE_API_USERNAME = os.environ.get('AA_DISCOURSE_API_USERNAME', '')
DISCOURSE_API_KEY = os.environ.get('AA_DISCOURSE_API_KEY', '')
DISCOURSE_SSO_SECRET = 'd836444a9e4084d5b224a60c208dce14'
# Example secret from https://meta.discourse.org/t/official-single-sign-on-for-discourse/13045
#####################################
# IPS4 Configuration
#####################################
# IPS4_URL - base url of the IPS4 install (no trailing slash)
# IPS4_API_KEY - API key provided by IPS4
#####################################
IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://example.com/ips4')
IPS4_API_KEY = os.environ.get('AA_IPS4_API_KEY', '')
#####################################
# SEAT Configuration
#####################################
# SEAT_URL - base url of the seat install (no trailing slash)
# SEAT_XTOKEN - API key X-Token provided by SeAT
#####################################
SEAT_URL = os.environ.get('AA_SEAT_URL', 'http://example.com/seat')
SEAT_XTOKEN = os.environ.get('AA_SEAT_XTOKEN', 'tokentokentoken')
######################################
# SMF Configuration
######################################
SMF_URL = os.environ.get('AA_SMF_URL', '')
######################################
# Fleet-Up Configuration
######################################
# FLEETUP_APP_KEY - The app key from http://fleet-up.com/Api/MyApps
# FLEETUP_USER_ID - The user id from http://fleet-up.com/Api/MyKeys
# FLEETUP_API_ID - The API id from http://fleet-up.com/Api/MyKeys
# FLEETUP_GROUP_ID - The id of the group you want to pull data from, see http://fleet-up.com/Api/Endpoints#groups_mygroupmemberships
######################################
FLEETUP_APP_KEY = os.environ.get('AA_FLEETUP_APP_KEY', '')
FLEETUP_USER_ID = os.environ.get('AA_FLEETUP_USER_ID', '')
FLEETUP_API_ID = os.environ.get('AA_FLEETUP_API_ID', '')
FLEETUP_GROUP_ID = os.environ.get('AA_FLEETUP_GROUP_ID', '')
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
'datefmt' : "%d/%b/%Y %H:%M:%S"
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'console': {
'level': 'DEBUG', # edit this line to change logging level to console
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'notifications': { # creates notifications for users with logging_notifications permission
'level': 'ERROR', # edit this line to change logging level to notifications
'class': 'notifications.handlers.NotificationHandler',
'formatter': 'verbose',
},
},
'loggers': {
'authentication': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'celerytask': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'eveonline': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'groupmanagement': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'hrapplications': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'portal': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'registration': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'services': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'srp': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'timerboard': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'sigtracker': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'optimer': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'corputils': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'fleetactivitytracking': {
'handlers': ['console', 'notifications'],
'level': 'ERROR',
},
'util': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'django': {
'handlers': ['console', 'notifications'],
'level': 'ERROR',
},
}
}
LOGGING = None # Comment out to enable logging for debugging

View File

@@ -10,13 +10,19 @@ import services.views
import groupmanagement.views import groupmanagement.views
import optimer.views import optimer.views
import timerboard.views import timerboard.views
import corputils.views
import fleetactivitytracking.views import fleetactivitytracking.views
import fleetup.views import fleetup.urls
import srp.views import srp.views
import notifications.views import notifications.views
import hrapplications.views import hrapplications.views
import eve_sso.urls import corputils.urls
import esi.urls
import permissions_tool.urls
from alliance_auth import NAME
admin.site.site_header = NAME
from alliance_auth.hooks import get_hooks
# Functional/Untranslated URL's # Functional/Untranslated URL's
urlpatterns = [ urlpatterns = [
@@ -27,8 +33,8 @@ urlpatterns = [
url(r'^admin/', include(admin.site.urls)), url(r'^admin/', include(admin.site.urls)),
# SSO # SSO
url (r'^sso/', include(eve_sso.urls, namespace='eve_sso')), url(r'^sso/', include(esi.urls, namespace='esi')),
url (r'^sso/login$', authentication.views.sso_login, name='auth_sso_login'), url(r'^sso/login$', authentication.views.sso_login, name='auth_sso_login'),
# Index # Index
url(_(r'^$'), authentication.views.index_view, name='auth_index'), url(_(r'^$'), authentication.views.index_view, name='auth_index'),
@@ -41,85 +47,6 @@ urlpatterns = [
name='auth_main_character_change'), name='auth_main_character_change'),
url(r'^api_verify_owner/(\w+)/$', eveonline.views.api_sso_validate, name='auth_api_sso'), url(r'^api_verify_owner/(\w+)/$', eveonline.views.api_sso_validate, name='auth_api_sso'),
# Forum Service Control
url(r'^activate_forum/$', services.views.activate_forum, name='auth_activate_forum'),
url(r'^deactivate_forum/$', services.views.deactivate_forum, name='auth_deactivate_forum'),
url(r'^reset_forum_password/$', services.views.reset_forum_password,
name='auth_reset_forum_password'),
url(r'^set_forum_password/$', services.views.set_forum_password, name='auth_set_forum_password'),
# Jabber Service Control
url(r'^activate_jabber/$', services.views.activate_jabber, name='auth_activate_jabber'),
url(r'^deactivate_jabber/$', services.views.deactivate_jabber, name='auth_deactivate_jabber'),
url(r'^reset_jabber_password/$', services.views.reset_jabber_password,
name='auth_reset_jabber_password'),
# Mumble service control
url(r'^activate_mumble/$', services.views.activate_mumble, name='auth_activate_mumble'),
url(r'^deactivate_mumble/$', services.views.deactivate_mumble, name='auth_deactivate_mumble'),
url(r'^reset_mumble_password/$', services.views.reset_mumble_password,
name='auth_reset_mumble_password'),
url(r'^set_mumble_password/$', services.views.set_mumble_password, name='auth_set_mumble_password'),
# Ipboard service control
url(r'^activate_ipboard/$', services.views.activate_ipboard_forum,
name='auth_activate_ipboard'),
url(r'^deactivate_ipboard/$', services.views.deactivate_ipboard_forum,
name='auth_deactivate_ipboard'),
url(r'^reset_ipboard_password/$', services.views.reset_ipboard_password,
name='auth_reset_ipboard_password'),
url(r'^set_ipboard_password/$', services.views.set_ipboard_password, name='auth_set_ipboard_password'),
# XenForo service control
url(r'^activate_xenforo/$', services.views.activate_xenforo_forum,
name='auth_activate_xenforo'),
url(r'^deactivate_xenforo/$', services.views.deactivate_xenforo_forum,
name='auth_deactivate_xenforo'),
url(r'^reset_xenforo_password/$', services.views.reset_xenforo_password,
name='auth_reset_xenforo_password'),
url(r'^set_xenforo_password/$', services.views.set_xenforo_password, name='auth_set_xenforo_password'),
# Teamspeak3 service control
url(r'^activate_teamspeak3/$', services.views.activate_teamspeak3,
name='auth_activate_teamspeak3'),
url(r'^deactivate_teamspeak3/$', services.views.deactivate_teamspeak3,
name='auth_deactivate_teamspeak3'),
url(r'reset_teamspeak3_perm/$', services.views.reset_teamspeak3_perm,
name='auth_reset_teamspeak3_perm'),
# Discord Service Control
url(r'^activate_discord/$', services.views.activate_discord, name='auth_activate_discord'),
url(r'^deactivate_discord/$', services.views.deactivate_discord, name='auth_deactivate_discord'),
url(r'^reset_discord/$', services.views.reset_discord, name='auth_reset_discord'),
url(r'^discord_callback/$', services.views.discord_callback, name='auth_discord_callback'),
url(r'^discord_add_bot/$', services.views.discord_add_bot, name='auth_discord_add_bot'),
# Discourse Service Control
url(r'^discourse_sso$', services.views.discourse_sso, name='auth_discourse_sso'),
# IPS4 Service Control
url(r'^activate_ips4/$', services.views.activate_ips4,
name='auth_activate_ips4'),
url(r'^deactivate_ips4/$', services.views.deactivate_ips4,
name='auth_deactivate_ips4'),
url(r'^reset_ips4_password/$', services.views.reset_ips4_password,
name='auth_reset_ips4_password'),
url(r'^set_ips4_password/$', services.views.set_ips4_password, name='auth_set_ips4_password'),
# SMF Service Control
url(r'^activate_smf/$', services.views.activate_smf, name='auth_activate_smf'),
url(r'^deactivate_smf/$', services.views.deactivate_smf, name='auth_deactivate_smf'),
url(r'^reset_smf_password/$', services.views.reset_smf_password,
name='auth_reset_smf_password'),
url(r'^set_smf_password/$', services.views.set_smf_password, name='auth_set_smf_password'),
# Alliance Market Control
url(r'^activate_market/$', services.views.activate_market, name='auth_activate_market'),
url(r'^deactivate_market/$', services.views.deactivate_market, name='auth_deactivate_market'),
url(r'^reset_market_password/$', services.views.reset_market_password,
name='auth_reset_market_password'),
url(r'^set_market_password/$', services.views.set_market_password, name='auth_set_market_password'),
# SRP URLS # SRP URLS
url(r'^srp_fleet_remove/(\w+)$', srp.views.srp_fleet_remove, name='auth_srp_fleet_remove'), url(r'^srp_fleet_remove/(\w+)$', srp.views.srp_fleet_remove, name='auth_srp_fleet_remove'),
url(r'^srp_fleet_disable/(\w+)$', srp.views.srp_fleet_disable, name='auth_srp_fleet_disable'), url(r'^srp_fleet_disable/(\w+)$', srp.views.srp_fleet_disable, name='auth_srp_fleet_disable'),
@@ -128,11 +55,14 @@ urlpatterns = [
name='auth_srp_fleet_mark_completed'), name='auth_srp_fleet_mark_completed'),
url(r'^srp_fleet_mark_uncompleted/(\w+)', srp.views.srp_fleet_mark_uncompleted, url(r'^srp_fleet_mark_uncompleted/(\w+)', srp.views.srp_fleet_mark_uncompleted,
name='auth_srp_fleet_mark_uncompleted'), name='auth_srp_fleet_mark_uncompleted'),
url(r'^srp_request_remove/(\w+)', srp.views.srp_request_remove, url(r'^srp_request_remove/', srp.views.srp_request_remove,
name="auth_srp_request_remove"), name="auth_srp_request_remove"),
url(r'srp_request_approve/(\w+)', srp.views.srp_request_approve, url(r'srp_request_approve/', srp.views.srp_request_approve,
name='auth_srp_request_approve'), name='auth_srp_request_approve'),
url(r'srp_request_reject/(\w+)', srp.views.srp_request_reject, name='auth_srp_request_reject'), url(r'srp_request_reject/', srp.views.srp_request_reject,
name='auth_srp_request_reject'),
url(_(r'srp_request_amount_update/(\w+)'), srp.views.srp_request_update_amount,
name="auth_srp_request_update_amount"),
# Notifications # Notifications
url(r'^remove_notifications/(\w+)/$', notifications.views.remove_notification, name='auth_remove_notification'), url(r'^remove_notifications/(\w+)/$', notifications.views.remove_notification, name='auth_remove_notification'),
@@ -144,21 +74,8 @@ urlpatterns = [
# User viewed/translated URLS # User viewed/translated URLS
urlpatterns += i18n_patterns( urlpatterns += i18n_patterns(
# corputils
url(r'^corputils/$', corputils.views.corp_member_view, name='auth_corputils'),
url(r'^corputils/(?P<corpid>[0-9]+)/$', corputils.views.corp_member_view, name='auth_corputils_corp_view'),
url(r'^corputils/(?P<corpid>[0-9]+)/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$', corputils.views.corp_member_view,
name='auth_corputils_month'),
url(r'^corputils/search/$', corputils.views.corputils_search, name="auth_corputils_search"),
url(r'^corputils/search/(?P<corpid>[0-9]+)/$', corputils.views.corputils_search, name='auth_corputils_search_corp'),
# Fleetup # Fleetup
url(r'^fleetup/$', fleetup.views.fleetup_view, name='auth_fleetup_view'), url(r'^fleetup/', include(fleetup.urls.urlpatterns)),
url(r'^fleetup/fittings/$', fleetup.views.fleetup_fittings, name='auth_fleetup_fittings'),
url(r'^fleetup/fittings/(?P<fittingnumber>[0-9]+)/$', fleetup.views.fleetup_fitting, name='auth_fleetup_fitting'),
url(r'^fleetup/doctrines/$', fleetup.views.fleetup_doctrines, name='auth_fleetup_doctrines'),
url(r'^fleetup/characters/$', fleetup.views.fleetup_characters, name='auth_fleetup_characters'),
url(r'^fleetup/doctrines/(?P<doctrinenumber>[0-9]+)/$', fleetup.views.fleetup_doctrine, name='auth_fleetup_doctrine'),
# Authentication # Authentication
url(_(r'^login_user/'), authentication.views.login_user, name='auth_login_user'), url(_(r'^login_user/'), authentication.views.login_user, name='auth_login_user'),
@@ -177,21 +94,28 @@ urlpatterns += i18n_patterns(
django.contrib.auth.views.password_reset_confirm, name='password_reset_confirm'), django.contrib.auth.views.password_reset_confirm, name='password_reset_confirm'),
# Portal Urls # Portal Urls
url(_(r'^dashboard/$'), authentication.views.dashboard_view, name='auth_dashboard'), url(_(r'^dashboard/$'), eveonline.views.dashboard_view, name='auth_dashboard'),
url(_(r'^help/$'), authentication.views.help_view, name='auth_help'), url(_(r'^help/$'), authentication.views.help_view, name='auth_help'),
# Eveonline Urls # Eveonline Urls
url(_(r'^add_api_key/'), eveonline.views.add_api_key, name='auth_add_api_key'), url(_(r'^add_api_key/'), eveonline.views.add_api_key, name='auth_add_api_key'),
url(_(r'^api_key_management/'), eveonline.views.api_key_management_view,
name='auth_api_key_management'),
url(_(r'^refresh_api_pair/([0-9]+)/$'), eveonline.views.user_refresh_api, name='auth_user_refresh_api'), url(_(r'^refresh_api_pair/([0-9]+)/$'), eveonline.views.user_refresh_api, name='auth_user_refresh_api'),
url(_(r'^delete_api_pair/(\w+)/$'), eveonline.views.api_key_removal, name='auth_api_key_removal'), url(_(r'^delete_api_pair/(\w+)/$'), eveonline.views.api_key_removal, name='auth_api_key_removal'),
url(_(r'^characters/'), eveonline.views.characters_view, name='auth_characters'), url(_(r'^characters/'), eveonline.views.characters_view, name='auth_characters'),
# Corputils
url(_(r'^corpstats/'), include(corputils.urls, namespace='corputils')),
# Group management # Group management
url(_(r'^groups/'), groupmanagement.views.groups_view, name='auth_groups'), url(_(r'^groups/'), groupmanagement.views.groups_view, name='auth_groups'),
url(_(r'^group/management/'), groupmanagement.views.group_management, url(_(r'^group/management/'), groupmanagement.views.group_management,
name='auth_group_management'), name='auth_group_management'),
url(_(r'^group/membership/$'), groupmanagement.views.group_membership,
name='auth_group_membership'),
url(_(r'^group/membership/(\w+)/$'), groupmanagement.views.group_membership_list,
name='auth_group_membership_list'),
url(_(r'^group/membership/(\w+)/remove/(\w+)/$'), groupmanagement.views.group_membership_remove,
name='auth_group_membership_remove'),
url(_(r'^group/request_add/(\w+)'), groupmanagement.views.group_request_add, url(_(r'^group/request_add/(\w+)'), groupmanagement.views.group_request_add,
name='auth_group_request_add'), name='auth_group_request_add'),
url(_(r'^group/request/accept/(\w+)'), groupmanagement.views.group_accept_request, url(_(r'^group/request/accept/(\w+)'), groupmanagement.views.group_accept_request,
@@ -239,17 +163,6 @@ urlpatterns += i18n_patterns(
# Service Urls # Service Urls
url(_(r'^services/$'), services.views.services_view, name='auth_services'), url(_(r'^services/$'), services.views.services_view, name='auth_services'),
url(_(r'^services/jabber_broadcast/$'), services.views.jabber_broadcast_view,
name='auth_jabber_broadcast_view'),
# Teamspeak Urls
url(r'verify_teamspeak3/$', services.views.verify_teamspeak3, name='auth_verify_teamspeak3'),
# corputils
url(_(r'^corputils/$'), corputils.views.corp_member_view, name='auth_corputils'),
url(_(r'^corputils/(?P<corpid>[0-9]+)/$'), corputils.views.corp_member_view, name='auth_corputils_corp_view'),
url(_(r'^corputils/search/$'), corputils.views.corputils_search, name="auth_corputils_search"),
url(_(r'^corputils/search/(?P<corpid>[0-9]+)/$'), corputils.views.corputils_search, name='auth_corputils_search_corp'),
# Timer URLS # Timer URLS
url(_(r'^timers/$'), timerboard.views.timer_view, name='auth_timer_view'), url(_(r'^timers/$'), timerboard.views.timer_view, name='auth_timer_view'),
@@ -264,8 +177,6 @@ urlpatterns += i18n_patterns(
url(_(r'^srp_fleet_add_view/$'), srp.views.srp_fleet_add_view, name='auth_srp_fleet_add_view'), url(_(r'^srp_fleet_add_view/$'), srp.views.srp_fleet_add_view, name='auth_srp_fleet_add_view'),
url(_(r'^srp_fleet_edit/(\w+)$'), srp.views.srp_fleet_edit_view, name='auth_srp_fleet_edit_view'), url(_(r'^srp_fleet_edit/(\w+)$'), srp.views.srp_fleet_edit_view, name='auth_srp_fleet_edit_view'),
url(_(r'^srp_request/(\w+)'), srp.views.srp_request_view, name='auth_srp_request_view'), url(_(r'^srp_request/(\w+)'), srp.views.srp_request_view, name='auth_srp_request_view'),
url(_(r'srp_request_amount_update/(\w+)'), srp.views.srp_request_update_amount_view,
name="auth_srp_request_update_amount_view"),
# Tools # Tools
url(_(r'^tool/fleet_formatter_tool/$'), services.views.fleet_formatter_view, url(_(r'^tool/fleet_formatter_tool/$'), services.views.fleet_formatter_view,
@@ -275,12 +186,12 @@ urlpatterns += i18n_patterns(
url(_(r'^notifications/$'), notifications.views.notification_list, name='auth_notification_list'), url(_(r'^notifications/$'), notifications.views.notification_list, name='auth_notification_list'),
url(_(r'^notifications/(\w+)/$'), notifications.views.notification_view, name='auth_notification_view'), url(_(r'^notifications/(\w+)/$'), notifications.views.notification_view, name='auth_notification_view'),
# Jabber
url(_(r'^set_jabber_password/$'), services.views.set_jabber_password, name='auth_set_jabber_password'),
# FleetActivityTracking (FAT) # FleetActivityTracking (FAT)
url(r'^fat/$', fleetactivitytracking.views.fatlink_view, name='auth_fatlink_view'), url(r'^fat/$', fleetactivitytracking.views.fatlink_view, name='auth_fatlink_view'),
url(r'^fat/statistics/$', fleetactivitytracking.views.fatlink_statistics_view, name='auth_fatlink_view_statistics'), url(r'^fat/statistics/$', fleetactivitytracking.views.fatlink_statistics_view, name='auth_fatlink_view_statistics'),
url(r'^fat/statistics/corp/(\w+)$', fleetactivitytracking.views.fatlink_statistics_corp_view, name='auth_fatlink_view_statistics_corp'),
url(r'^fat/statistics/corp/(?P<corpid>\w+)/(?P<year>[0-9]+)/(?P<month>[0-9]+)/', fleetactivitytracking.views.fatlink_statistics_corp_view,
name='auth_fatlink_view_statistics_corp_month'),
url(r'^fat/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$', fleetactivitytracking.views.fatlink_statistics_view, url(r'^fat/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$', fleetactivitytracking.views.fatlink_statistics_view,
name='auth_fatlink_view_statistics_month'), name='auth_fatlink_view_statistics_month'),
url(r'^fat/user/statistics/$', fleetactivitytracking.views.fatlink_personal_statistics_view, url(r'^fat/user/statistics/$', fleetactivitytracking.views.fatlink_personal_statistics_view,
@@ -300,4 +211,12 @@ urlpatterns += i18n_patterns(
url(r'^fat/link/$', fleetactivitytracking.views.fatlink_view, name='auth_click_fatlink_view'), url(r'^fat/link/$', fleetactivitytracking.views.fatlink_view, name='auth_click_fatlink_view'),
url(r'^fat/link/(?P<hash>[a-zA-Z0-9]+)/(?P<fatname>[a-z0-9_-]+)/$', url(r'^fat/link/(?P<hash>[a-zA-Z0-9]+)/(?P<fatname>[a-z0-9_-]+)/$',
fleetactivitytracking.views.click_fatlink_view), fleetactivitytracking.views.click_fatlink_view),
url(r'^permissions/', include(permissions_tool.urls))
) )
# Append hooked service urls
services = get_hooks('services_hook')
for svc in services:
urlpatterns += svc().urlpatterns

View File

@@ -1,22 +1,19 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.utils.text import slugify
from authentication.models import AuthServicesInfo from authentication.models import AuthServicesInfo
from eveonline.models import EveCharacter from eveonline.models import EveCharacter
from services.tasks import update_jabber_groups from alliance_auth.hooks import get_hooks
from services.tasks import update_mumble_groups from services.hooks import ServicesHook
from services.tasks import update_forum_groups
from services.tasks import update_ipboard_groups
from services.tasks import update_smf_groups
from services.tasks import update_teamspeak3_groups
from services.tasks import update_discord_groups
from services.tasks import update_discord_nickname
from services.tasks import update_discourse_groups
@admin.register(AuthServicesInfo) @admin.register(AuthServicesInfo)
class AuthServicesInfoManager(admin.ModelAdmin): class AuthServicesInfoManager(admin.ModelAdmin):
@staticmethod @staticmethod
def main_character(obj): def main_character(obj):
if obj.main_char_id: if obj.main_char_id:
@@ -26,120 +23,77 @@ class AuthServicesInfoManager(admin.ModelAdmin):
pass pass
return None return None
def sync_jabber(self, request, queryset): @staticmethod
count = 0 def has_delete_permission(request, obj=None):
for a in queryset: # queryset filtering doesn't work here? return False
if a.jabber_username != "":
update_jabber_groups.delay(a.user.pk)
count += 1
self.message_user(request, "%s jabber accounts queued for group sync." % count)
sync_jabber.short_description = "Sync groups for selected jabber accounts" @staticmethod
def has_add_permission(request, obj=None):
def sync_mumble(self, request, queryset): return False
count = 0
for a in queryset:
if a.mumble_username != "":
update_mumble_groups.delay(a.user.pk)
count += 1
self.message_user(request, "%s mumble accounts queued for group sync." % count)
sync_mumble.short_description = "Sync groups for selected mumble accounts"
def sync_forum(self, request, queryset):
count = 0
for a in queryset:
if a.forum_username != "":
update_forum_groups.delay(a.user.pk)
count += 1
self.message_user(request, "%s forum accounts queued for group sync." % count)
sync_forum.short_description = "Sync groups for selected forum accounts"
def sync_ipboard(self, request, queryset):
count = 0
for a in queryset:
if a.ipboard_username != "":
update_ipboard_groups.delay(a.user.pk)
count += 1
self.message_user(request, "%s ipboard accounts queued for group sync." % count)
sync_ipboard.short_description = "Sync groups for selected ipboard accounts"
def sync_smf(self, request, queryset):
count = 0
for a in queryset:
if a.smf_username != "":
update_smf_groups.delay(a.user.pk)
count += 1
self.message_user(request, "%s smf accounts queued for group sync." % count)
sync_smf.short_description = "Sync groups for selected smf accounts"
def sync_teamspeak(self, request, queryset):
count = 0
for a in queryset:
if a.teamspeak3_uid != "":
update_teamspeak3_groups.delay(a.user.pk)
count += 1
self.message_user(request, "%s teamspeak accounts queued for group sync." % count)
sync_teamspeak.short_description = "Sync groups for selected teamspeak accounts"
def sync_discord(self, request, queryset):
count = 0
for a in queryset:
if a.discord_uid != "":
update_discord_groups.delay(a.user.pk)
count += 1
self.message_user(request, "%s discord accounts queued for group sync." % count)
sync_discord.short_description = "Sync groups for selected discord accounts"
def sync_discourse(self, request, queryset):
count = 0
for a in queryset:
if a.discourse_enabled:
update_discourse_groups.delay(a.user.pk)
count += 1
self.message_user(request, "%s discourse accounts queued for group sync." % count)
sync_discourse.short_description = "Sync groups for selected discourse accounts"
def sync_nicknames(self, request, queryset):
count = 0
for a in queryset:
if a.discord_uid != "":
update_discord_nickname(a.user.pk)
count += 1
self.message_user(request, "%s discord accounts queued for nickname sync." % count)
sync_nicknames.short_description = "Sync nicknames for selected discord accounts"
actions = [
'sync_jabber',
'sync_mumble',
'sync_forum',
'sync_ipboard',
'sync_smf',
'sync_teamspeak',
'sync_discord',
'sync_discourse',
'sync_nicknames',
]
search_fields = [ search_fields = [
'user__username', 'user__username',
'ipboard_username',
'xenforo_username',
'forum_username',
'jabber_username',
'mumble_username',
'teamspeak3_uid',
'discord_uid',
'ips4_username',
'smf_username',
'market_username',
'main_char_id', 'main_char_id',
] ]
list_display = ('user', 'main_character') list_display = ('user', 'main_character')
def make_service_hooks_update_groups_action(service):
"""
Make a admin action for the given service
:param service: services.hooks.ServicesHook
:return: fn to update services groups for the selected users
"""
def update_service_groups(modeladmin, request, queryset):
for user in queryset: # queryset filtering doesn't work here?
service.update_groups(user)
update_service_groups.__name__ = str('update_{}_groups'.format(slugify(service.name)))
update_service_groups.short_description = "Sync groups for selected {} accounts".format(service.title)
return update_service_groups
def make_service_hooks_sync_nickname_action(service):
"""
Make a sync_nickname admin action for the given service
:param service: services.hooks.ServicesHook
:return: fn to sync nickname for the selected users
"""
def sync_nickname(modeladmin, request, queryset):
for user in queryset: # queryset filtering doesn't work here?
service.sync_nickname(user)
sync_nickname.__name__ = str('sync_{}_nickname'.format(slugify(service.name)))
sync_nickname.short_description = "Sync nicknames for selected {} accounts".format(service.title)
return sync_nickname
class UserAdmin(BaseUserAdmin):
"""
Extending Django's UserAdmin model
"""
def get_actions(self, request):
actions = super(BaseUserAdmin, self).get_actions(request)
for hook in get_hooks('services_hook'):
svc = hook()
# Check update_groups is redefined/overloaded
if svc.update_groups.__module__ != ServicesHook.update_groups.__module__:
action = make_service_hooks_update_groups_action(svc)
actions[action.__name__] = (action,
action.__name__,
action.short_description)
# Create sync nickname action if service implements it
if svc.sync_nickname.__module__ != ServicesHook.sync_nickname.__module__:
action = make_service_hooks_sync_nickname_action(svc)
actions[action.__name__] = (action,
action.__name__,
action.short_description)
return actions
# Re-register UserAdmin
try:
admin.site.unregister(User)
finally:
admin.site.register(User, UserAdmin)

View File

@@ -1,14 +1,11 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from authentication.models import AuthServicesInfo
from authentication.states import NONE_STATE, BLUE_STATE, MEMBER_STATE from authentication.states import NONE_STATE, BLUE_STATE, MEMBER_STATE
from authentication.managers import UserState
from django.conf import settings from django.conf import settings
def membership_state(request): def membership_state(request):
if request.user.is_authenticated: return UserState.get_membership_state(request)
auth = AuthServicesInfo.objects.get_or_create(user=request.user)[0]
return {'STATE': auth.state}
return {'STATE': NONE_STATE}
def states(request): def states(request):
@@ -18,8 +15,3 @@ def states(request):
'NONE_STATE': NONE_STATE, 'NONE_STATE': NONE_STATE,
'MEMBER_BLUE_STATE': [MEMBER_STATE, BLUE_STATE], 'MEMBER_BLUE_STATE': [MEMBER_STATE, BLUE_STATE],
} }
def sso(request):
return {
'EVE_SSO_CALLBACK_URL': settings.EVE_SSO_CALLBACK_URL,
}

View File

@@ -1,33 +1,23 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from authentication.models import AuthServicesInfo from authentication.managers import UserState
from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
from django.conf import settings
def _state_required(states, *args, **kwargs): def _state_required(state_test, *args, **kwargs):
def test_func(user): return user_passes_test(state_test, *args, **kwargs)
if user.is_superuser and settings.SUPERUSER_STATE_BYPASS:
return True
if user.is_authenticated:
auth = AuthServicesInfo.objects.get_or_create(user=user)[0]
return auth.state in states
return False
return user_passes_test(test_func, *args, **kwargs)
def members(*args, **kwargs): def members(*args, **kwargs):
return _state_required([MEMBER_STATE], *args, **kwargs) return _state_required(UserState.member_state, *args, **kwargs)
def blues(*args, **kwargs): def blues(*args, **kwargs):
return _state_required([BLUE_STATE], *args, **kwargs) return _state_required(UserState.blue_state, *args, **kwargs)
def members_and_blues(*args, **kwargs): def members_and_blues(*args, **kwargs):
return _state_required([MEMBER_STATE, BLUE_STATE], *args, **kwargs) return _state_required(UserState.member_or_blue_state, *args, **kwargs)
def none_state(*args, **kwargs): def none_state(*args, **kwargs):
return _state_required([NONE_STATE], *args, **kwargs) return _state_required(UserState.none_state, *args, **kwargs)

View File

@@ -2,6 +2,7 @@ from __future__ import unicode_literals
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings
import re import re
@@ -9,6 +10,10 @@ class LoginForm(forms.Form):
username = forms.CharField(label=_('Username'), max_length=32, required=True) username = forms.CharField(label=_('Username'), max_length=32, required=True)
password = forms.CharField(label=_('Password'), widget=forms.PasswordInput()) password = forms.CharField(label=_('Password'), widget=forms.PasswordInput())
if getattr(settings, 'CAPTCHA_ENABLED', False):
from captcha.fields import ReCaptchaField
captcha = ReCaptchaField()
class RegistrationForm(forms.Form): class RegistrationForm(forms.Form):
username = forms.CharField(label=_('Username'), max_length=30, required=True) username = forms.CharField(label=_('Username'), max_length=30, required=True)
@@ -17,16 +22,16 @@ class RegistrationForm(forms.Form):
email = forms.CharField(label=_('Email'), max_length=254, required=True) email = forms.CharField(label=_('Email'), max_length=254, required=True)
email_again = forms.CharField(label=_('Email Again'), max_length=254, required=True) email_again = forms.CharField(label=_('Email Again'), max_length=254, required=True)
if getattr(settings, 'CAPTCHA_ENABLED', False):
from captcha.fields import ReCaptchaField
captcha = ReCaptchaField()
def clean(self): def clean(self):
if ' ' in self.cleaned_data['username']: if ' ' in self.cleaned_data['username']:
raise forms.ValidationError('Username cannot contain a space') raise forms.ValidationError('Username cannot contain a space')
# We attempt to get the user object if we succeed we know email as been used if User.objects.filter(email=self.cleaned_data['email']).count() >= 1:
try:
User.objects.get(email=self.cleaned_data['email'])
raise forms.ValidationError('Email as already been used') raise forms.ValidationError('Email as already been used')
except User.DoesNotExist:
pass
if not re.match("^\w+$", self.cleaned_data['username']): if not re.match("^\w+$", self.cleaned_data['username']):
raise forms.ValidationError('Username contains illegal characters') raise forms.ValidationError('Username contains illegal characters')

View File

@@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings
from authentication.states import NONE_STATE, BLUE_STATE, MEMBER_STATE
from authentication.models import AuthServicesInfo from authentication.models import AuthServicesInfo
import logging import logging
@@ -16,130 +17,59 @@ class AuthServicesInfoManager:
def update_main_char_id(char_id, user): def update_main_char_id(char_id, user):
if User.objects.filter(username=user.username).exists(): if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s main character to id %s" % (user, char_id)) logger.debug("Updating user %s main character to id %s" % (user, char_id))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0] authserviceinfo = AuthServicesInfo.objects.get(user=user)
authserviceinfo.main_char_id = char_id authserviceinfo.main_char_id = char_id
authserviceinfo.save(update_fields=['main_char_id']) authserviceinfo.save(update_fields=['main_char_id'])
logger.info("Updated user %s main character to id %s" % (user, char_id)) logger.info("Updated user %s main character to id %s" % (user, char_id))
else: else:
logger.error("Failed to update user %s main character id to %s: user does not exist." % (user, char_id)) logger.error("Failed to update user %s main character id to %s: user does not exist." % (user, char_id))
@staticmethod
def update_user_forum_info(username, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s forum info: username %s" % (user, username))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0]
authserviceinfo.forum_username = username
authserviceinfo.save(update_fields=['forum_username'])
logger.info("Updated user %s forum info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s forum info: user does not exist." % user)
@staticmethod
def update_user_jabber_info(username, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s jabber info: username %s" % (user, username))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0]
authserviceinfo.jabber_username = username
authserviceinfo.save(update_fields=['jabber_username'])
logger.info("Updated user %s jabber info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s jabber info: user does not exist." % user)
@staticmethod
def update_user_mumble_info(username, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s mumble info: username %s" % (user, username))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0]
authserviceinfo.mumble_username = username
authserviceinfo.save(update_fields=['mumble_username'])
logger.info("Updated user %s mumble info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s mumble info: user does not exist." % user)
@staticmethod
def update_user_ipboard_info(username, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s ipboard info: uername %s" % (user, username))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0]
authserviceinfo.ipboard_username = username
authserviceinfo.save(update_fields=['ipboard_username'])
logger.info("Updated user %s ipboard info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s ipboard info: user does not exist." % user)
@staticmethod
def update_user_xenforo_info(username, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s xenforo info: uername %s" % (user, username))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0]
authserviceinfo.xenforo_username = username
authserviceinfo.save(update_fields=['xenforo_username'])
logger.info("Updated user %s xenforo info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s xenforo info: user does not exist." % user)
@staticmethod
def update_user_teamspeak3_info(uid, perm_key, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s teamspeak3 info: uid %s" % (user, uid))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0]
authserviceinfo.teamspeak3_uid = uid
authserviceinfo.teamspeak3_perm_key = perm_key
authserviceinfo.save(update_fields=['teamspeak3_uid', 'teamspeak3_perm_key'])
logger.info("Updated user %s teamspeak3 info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s teamspeak3 info: user does not exist." % user)
@staticmethod @staticmethod
def update_is_blue(is_blue, user): def update_is_blue(is_blue, user):
if User.objects.filter(username=user.username).exists(): if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s blue status: %s" % (user, is_blue)) logger.debug("Updating user %s blue status: %s" % (user, is_blue))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0] authserviceinfo = AuthServicesInfo.objects.get(user=user)
authserviceinfo.is_blue = is_blue authserviceinfo.is_blue = is_blue
authserviceinfo.save(update_fields=['is_blue']) authserviceinfo.save(update_fields=['is_blue'])
logger.info("Updated user %s blue status to %s in authservicesinfo model." % (user, is_blue)) logger.info("Updated user %s blue status to %s in authservicesinfo model." % (user, is_blue))
@staticmethod
def update_user_discord_info(user_id, user): class UserState:
if User.objects.filter(username=user.username).exists(): def __init__(self):
logger.debug("Updating user %s discord info: user_id %s" % (user, user_id)) pass
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0]
authserviceinfo.discord_uid = user_id MEMBER_STATE = MEMBER_STATE
authserviceinfo.save(update_fields=['discord_uid']) BLUE_STATE = BLUE_STATE
logger.info("Updated user %s discord info in authservicesinfo model." % user) NONE_STATE = NONE_STATE
else:
logger.error("Failed to update user %s discord info: user does not exist." % user) @classmethod
def member_state(cls, user):
return cls.state_required(user, [cls.MEMBER_STATE])
@classmethod
def member_or_blue_state(cls, user):
return cls.state_required(user, [cls.MEMBER_STATE, cls.BLUE_STATE])
@classmethod
def blue_state(cls, user):
return cls.state_required(user, [cls.BLUE_STATE])
@classmethod
def none_state(cls, user):
return cls.state_required(user, [cls.NONE_STATE])
@classmethod
def get_membership_state(cls, request):
if request.user.is_authenticated:
auth = AuthServicesInfo.objects.get(user=request.user)
return {'STATE': auth.state}
return {'STATE': cls.NONE_STATE}
@staticmethod @staticmethod
def update_user_ips4_info(username, id, user): def state_required(user, states):
if User.objects.filter(username=user.username).exists(): if user.is_superuser and settings.SUPERUSER_STATE_BYPASS:
logger.debug("Updating user %s IPS4 info: username %s" % (user, username)) return True
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0] if user.is_authenticated:
authserviceinfo.ips4_username = username auth = AuthServicesInfo.objects.get(user=user)
authserviceinfo.ips4_id = id return auth.state in states
authserviceinfo.save(update_fields=['ips4_username', 'ips4_id']) return False
logger.info("Updated user %s IPS4 info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s IPS4 info: user does not exist." % user)
@staticmethod
def update_user_smf_info(username, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s forum info: username %s" % (user, username))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0]
authserviceinfo.smf_username = username
authserviceinfo.save(update_fields=['smf_username'])
logger.info("Updated user %s smf info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s smf info: user does not exist." % user)
@staticmethod
def update_user_market_info(username, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s market info: username %s" % (user, username))
authserviceinfo = AuthServicesInfo.objects.get_or_create(user=user)[0]
authserviceinfo.market_username = username
authserviceinfo.save(update_fields=['market_username'])
logger.info("Updated user %s market info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s market info: user does not exist." % user)

View File

@@ -8,12 +8,10 @@ from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
from django.conf import settings from django.conf import settings
def determine_membership_by_character(char, apps): def determine_membership_by_character(char, apps):
if settings.IS_CORP: if str(char.corporation_id) in settings.STR_CORP_IDS:
if int(char.corporation_id) == int(settings.CORP_ID): return MEMBER_STATE
return MEMBER_STATE elif str(char.alliance_id) in settings.STR_ALLIANCE_IDS:
else: return MEMBER_STATE
if int(char.alliance_id) == int(settings.ALLIANCE_ID):
return MEMBER_STATE
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo') EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
if EveCorporationInfo.objects.filter(corporation_id=char.corporation_id).exists() is False: if EveCorporationInfo.objects.filter(corporation_id=char.corporation_id).exists() is False:
return NONE_STATE return NONE_STATE
@@ -26,7 +24,7 @@ def determine_membership_by_character(char, apps):
def determine_membership_by_user(user, apps): def determine_membership_by_user(user, apps):
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo') AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
auth, c = AuthServicesInfo.objects.get_or_create(user=user) auth = AuthServicesInfo.objects.get(user=user)
if auth.main_char_id: if auth.main_char_id:
EveCharacter = apps.get_model('eveonline', 'EveCharacter') EveCharacter = apps.get_model('eveonline', 'EveCharacter')
if EveCharacter.objects.filter(character_id=auth.main_char_id).exists(): if EveCharacter.objects.filter(character_id=auth.main_char_id).exists():
@@ -43,7 +41,7 @@ def set_state(user, apps):
else: else:
state = NONE_STATE state = NONE_STATE
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo') AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
auth = AuthServicesInfo.objects.get_or_create(user=user)[0] auth = AuthServicesInfo.objects.get(user=user)
if auth.state != state: if auth.state != state:
auth.state = state auth.state = state
auth.save() auth.save()

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-07 06:47
from __future__ import unicode_literals
from django.db import migrations
def count_completed_fields(model):
return len([True for key, value in model.__dict__.items() if bool(value)])
def forward(apps, schema_editor):
# this ensures only one model exists per user
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
users = set([a.user for a in AuthServicesInfo.objects.all()])
for u in users:
auths = AuthServicesInfo.objects.filter(user=u)
if auths.count() > 1:
pk = auths[0].pk
largest = 0
for auth in auths:
completed = count_completed_fields(auth)
if completed > largest:
largest = completed
pk = auth.pk
auths.exclude(pk=pk).delete()
# ensure all users have a model
User = apps.get_model('auth', 'User')
for u in User.objects.exclude(pk__in=[user.pk for user in users]):
AuthServicesInfo.objects.create(user=u)
class Migration(migrations.Migration):
dependencies = [
('authentication', '0009_auto_20161021_0228'),
]
operations = [
migrations.RunPython(forward, migrations.RunPython.noop)
]

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-07 07:11
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('authentication', '0010_only_one_authservicesinfo'),
]
operations = [
migrations.AlterField(
model_name='authservicesinfo',
name='user',
field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
]

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-01-12 00:59
from __future__ import unicode_literals
from django.db import migrations, models
def remove_permissions(apps, schema_editor):
ContentType = apps.get_model('contenttypes', 'ContentType')
Permission = apps.get_model('auth', 'Permission')
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
# delete the add and remove permissions for AuthServicesInfo
ct = ContentType.objects.get_for_model(AuthServicesInfo)
Permission.objects.filter(content_type=ct).filter(codename__in=['add_authservicesinfo', 'delete_authservicesinfo']).delete()
def add_permissions(apps, schema_editor):
ContentType = apps.get_model('contenttypes', 'ContentType')
Permission = apps.get_model('auth', 'Permission')
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
# recreate the add and remove permissions for AuthServicesInfo
ct = ContentType.objects.get_for_model(AuthServicesInfo)
Permission.objects.create(content_type=ct, codename='add_authservicesinfo', name='Can add auth services info')
Permission.objects.create(content_type=ct, codename='delete_authservicesinfo', name='Can delete auth services info')
class Migration(migrations.Migration):
dependencies = [
('authentication', '0011_authservicesinfo_user_onetoonefield'),
]
operations = [
migrations.AlterModelOptions(
name='authservicesinfo',
options={'default_permissions': ('change',)},
),
migrations.RunPython(remove_permissions, add_permissions),
]

View File

@@ -0,0 +1,323 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-12-11 23:14
from __future__ import unicode_literals
from django.db import migrations
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
import logging
logger = logging.getLogger(__name__)
def optional_dependencies():
"""
Only require these migrations if the given app
is installed. If the app isn't installed then
the relevant AuthServicesInfo field will be LOST
when the data migration is run.
"""
installed_apps = settings.INSTALLED_APPS
dependencies = []
# Skip adding module dependencies if the settings specifies that services have been migrated
if hasattr(settings, 'SERVICES_MIGRATED') and settings.SERVICES_MIGRATED:
return dependencies
if 'services.modules.xenforo' in installed_apps:
dependencies.append(('xenforo', '0001_initial'))
if 'services.modules.discord' in installed_apps:
dependencies.append(('discord', '0001_initial'))
if 'services.modules.discourse' in installed_apps:
dependencies.append(('discourse', '0001_initial'))
if 'services.modules.ipboard' in installed_apps:
dependencies.append(('ipboard', '0001_initial'))
if 'services.modules.ips4' in installed_apps:
dependencies.append(('ips4', '0001_initial'))
if 'services.modules.market' in installed_apps:
dependencies.append(('market', '0001_initial'))
if 'services.modules.openfire' in installed_apps:
dependencies.append(('openfire', '0001_initial'))
if 'services.modules.smf' in installed_apps:
dependencies.append(('smf', '0001_initial'))
if 'services.modules.teamspeak3' in installed_apps:
dependencies.append(('teamspeak3', '0003_teamspeak3user'))
if 'services.modules.mumble' in installed_apps:
dependencies.append(('mumble', '0003_mumbleuser_user'))
if 'services.modules.phpbb3' in installed_apps:
dependencies.append(('phpbb3', '0001_initial'))
return dependencies
class DatalossException(Exception):
def __init__(self, field, app):
message = "This migration would cause a loss of data for the %s field, ensure the %s app is installed and" \
" run the migration again" % (field, app)
super(Exception, self).__init__(message)
def check_for_dataloss(authservicesinfo):
"""
Check if any of the authservicesinfo contain a field which contains data
that would be lost because the target module for migration is not installed.
If a record is found with no matching target installed an exception is raised.
:param authservicesinfo: AuthServicesInfo records to check
"""
installed_apps = settings.INSTALLED_APPS
for authinfo in authservicesinfo:
if authinfo.xenforo_username and 'services.modules.xenforo' not in installed_apps:
raise DatalossException('xenforo_username', 'services.modules.xenforo')
if authinfo.discord_uid and 'services.modules.discord' not in installed_apps:
raise DatalossException('discord_uid', 'services.modules.discord')
if authinfo.discourse_enabled and 'services.modules.discourse' not in installed_apps:
raise DatalossException('discourse_enabled', 'services.modules.discourse')
if authinfo.ipboard_username and 'services.modules.ipboard' not in installed_apps:
raise DatalossException('ipboard_username', 'services.modules.ipboard')
if authinfo.ips4_id and 'services.modules.ips4' not in installed_apps:
raise DatalossException('ips4_id', 'services.modules.ips4')
if authinfo.market_username and 'services.modules.market' not in installed_apps:
raise DatalossException('market_username', 'services.modules.market')
if authinfo.jabber_username and 'services.modules.openfire' not in installed_apps:
raise DatalossException('jabber_username', 'services.modules.openfire')
if authinfo.smf_username and 'services.modules.smf' not in installed_apps:
raise DatalossException('smf_username', 'services.modules.smf')
if authinfo.teamspeak3_uid and 'services.modules.teamspeak3' not in installed_apps:
raise DatalossException('teamspeak3_uid', 'services.modules.teamspeak3')
if authinfo.mumble_username and 'services.modules.mumble' not in installed_apps:
raise DatalossException('mumble_username', 'services.modules.mumble')
if authinfo.forum_username and 'services.modules.phpbb3' not in installed_apps:
raise DatalossException('forum_username', 'services.modules.phpbb3')
def forward(apps, schema_editor):
installed_apps = settings.INSTALLED_APPS
AuthServicesInfo = apps.get_model("authentication", "AuthServicesInfo")
# Check if any records would result in dataloss
check_for_dataloss(AuthServicesInfo.objects.all())
XenforoUser = apps.get_model('xenforo', 'XenforoUser') if 'services.modules.xenforo' in installed_apps else None
DiscordUser = apps.get_model('discord', 'DiscordUser') if 'services.modules.discord' in installed_apps else None
DiscourseUser = apps.get_model('discourse', 'DiscourseUser') if 'services.modules.discourse' in installed_apps else None
IpboardUser = apps.get_model('ipboard', 'IpboardUser') if 'services.modules.ipboard' in installed_apps else None
Ips4User = apps.get_model('ips4', 'Ips4User') if 'services.modules.ips4' in installed_apps else None
MarketUser = apps.get_model('market', 'MarketUser') if 'services.modules.market' in installed_apps else None
OpenfireUser = apps.get_model('openfire', 'OpenfireUser') if 'services.modules.openfire' in installed_apps else None
SmfUser = apps.get_model('smf', 'SmfUser') if 'services.modules.smf' in installed_apps else None
Teamspeak3User = apps.get_model('teamspeak3', 'Teamspeak3User') if 'services.modules.teamspeak3' in installed_apps else None
MumbleUser = apps.get_model('mumble', 'MumbleUser') if 'services.modules.mumble' in installed_apps else None
Phpbb3User = apps.get_model('phpbb3', 'Phpbb3User') if 'services.modules.phpbb3' in installed_apps else None
for authinfo in AuthServicesInfo.objects.all():
user = authinfo.user
if XenforoUser is not None and authinfo.xenforo_username:
logging.debug('Updating Xenforo info for %s' % user.username)
xfu = XenforoUser()
xfu.user = user
xfu.username = authinfo.xenforo_username
xfu.save()
if DiscordUser is not None and authinfo.discord_uid:
logging.debug('Updating Discord info for %s' % user.username)
du = DiscordUser()
du.user = user
du.uid = authinfo.discord_uid
du.save()
if DiscourseUser is not None and authinfo.discourse_enabled:
logging.debug('Updating Discourse info for %s' % user.username)
du = DiscourseUser()
du.user = user
du.enabled = authinfo.discourse_enabled
du.save()
if IpboardUser is not None and authinfo.ipboard_username:
logging.debug('Updating IPBoard info for %s' % user.username)
ipb = IpboardUser()
ipb.user = user
ipb.username = authinfo.ipboard_username
ipb.save()
if Ips4User is not None and authinfo.ips4_id:
logging.debug('Updating Ips4 info for %s' % user.username)
ips = Ips4User()
ips.user = user
ips.id = authinfo.ips4_id
ips.username = authinfo.ips4_username
ips.save()
if MarketUser is not None and authinfo.market_username:
logging.debug('Updating Market info for %s' % user.username)
mkt = MarketUser()
mkt.user = user
mkt.username = authinfo.market_username
mkt.save()
if OpenfireUser is not None and authinfo.jabber_username:
logging.debug('Updating Openfire (jabber) info for %s' % user.username)
ofu = OpenfireUser()
ofu.user = user
ofu.username = authinfo.jabber_username
ofu.save()
if SmfUser is not None and authinfo.smf_username:
logging.debug('Updating SMF info for %s' % user.username)
smf = SmfUser()
smf.user = user
smf.username = authinfo.smf_username
smf.save()
if Teamspeak3User is not None and authinfo.teamspeak3_uid:
logging.debug('Updating Teamspeak3 info for %s' % user.username)
ts3 = Teamspeak3User()
ts3.user = user
ts3.uid = authinfo.teamspeak3_uid
ts3.perm_key = authinfo.teamspeak3_perm_key
ts3.save()
if MumbleUser is not None and authinfo.mumble_username:
logging.debug('Updating mumble info for %s' % user.username)
try:
mbl = MumbleUser.objects.get(username=authinfo.mumble_username)
mbl.user = user
mbl.save()
except ObjectDoesNotExist:
logger.warn('AuthServiceInfo mumble_username for {} but no '
'corresponding record in MumbleUser, dropping'.format(user.username))
if Phpbb3User is not None and authinfo.forum_username:
logging.debug('Updating phpbb3 info for %s' % user.username)
phb = Phpbb3User()
phb.user = user
phb.username = authinfo.forum_username
phb.save()
def reverse(apps, schema_editor):
User = apps.get_model('auth', 'User')
AuthServicesInfo = apps.get_model("authentication", "AuthServicesInfo")
for user in User.objects.all():
authinfo, c = AuthServicesInfo.objects.get_or_create(user=user)
if hasattr(user, 'xenforo'):
logging.debug('Reversing xenforo for %s' % user.username)
authinfo.xenforo_username = user.xenforo.username
if hasattr(user, 'discord'):
logging.debug('Reversing discord for %s' % user.username)
authinfo.discord_uid = user.discord.uid
if hasattr(user, 'discourse'):
logging.debug('Reversing discourse for %s' % user.username)
authinfo.discourse_enabled = user.discourse.enabled
if hasattr(user, 'ipboard'):
logging.debug('Reversing ipboard for %s' % user.username)
authinfo.ipboard_username = user.ipboard.username
if hasattr(user, 'ips4'):
logging.debug('Reversing ips4 for %s' % user.username)
authinfo.ips4_id = user.ips4.id
authinfo.ips4_username = user.ips4.username
if hasattr(user, 'market'):
logging.debug('Reversing market for %s' % user.username)
authinfo.market_username = user.market.username
if hasattr(user, 'openfire'):
logging.debug('Reversing openfire (jabber) for %s' % user.username)
authinfo.jabber_username = user.openfire.username
if hasattr(user, 'smf'):
logging.debug('Reversing smf for %s' % user.username)
authinfo.smf_username = user.smf.username
if hasattr(user, 'teamspeak3'):
logging.debug('Reversing teamspeak3 for %s' % user.username)
authinfo.teamspeak3_uid = user.teamspeak3.uid
authinfo.teamspeak3_perm_key = user.teamspeak3.perm_key
if hasattr(user, 'mumble'):
logging.debug('Reversing mumble for %s' % user.username)
try:
authinfo.mumble_username = user.mumble.all()[:1].get().username
except ObjectDoesNotExist:
logging.debug('Failed to reverse mumble for %s' % user.username)
if hasattr(user, 'phpbb3'):
logging.debug('Reversing phpbb3 for %s' % user.username)
authinfo.forum_username = user.phpbb3.username
logging.debug('Saving AuthServicesInfo for %s ' % user.username)
authinfo.save()
class Migration(migrations.Migration):
dependencies = optional_dependencies() + [
('authentication', '0012_remove_add_delete_authservicesinfo_permissions'),
]
operations = [
# Migrate data
migrations.RunPython(forward, reverse),
# Remove fields
migrations.RemoveField(
model_name='authservicesinfo',
name='discord_uid',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='discourse_enabled',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='forum_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='ipboard_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='ips4_id',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='ips4_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='jabber_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='market_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='mumble_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='smf_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='teamspeak3_perm_key',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='teamspeak3_uid',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='xenforo_username',
),
]

View File

@@ -7,27 +7,17 @@ from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
@python_2_unicode_compatible @python_2_unicode_compatible
class AuthServicesInfo(models.Model): class AuthServicesInfo(models.Model):
class Meta:
default_permissions = ('change',)
STATE_CHOICES = ( STATE_CHOICES = (
(NONE_STATE, 'None'), (NONE_STATE, 'None'),
(BLUE_STATE, 'Blue'), (BLUE_STATE, 'Blue'),
(MEMBER_STATE, 'Member'), (MEMBER_STATE, 'Member'),
) )
ipboard_username = models.CharField(max_length=254, blank=True, default="")
xenforo_username = models.CharField(max_length=254, blank=True, default="")
forum_username = models.CharField(max_length=254, blank=True, default="")
jabber_username = models.CharField(max_length=254, blank=True, default="")
mumble_username = models.CharField(max_length=254, blank=True, default="")
teamspeak3_uid = models.CharField(max_length=254, blank=True, default="")
teamspeak3_perm_key = models.CharField(max_length=254, blank=True, default="")
discord_uid = models.CharField(max_length=254, blank=True, default="")
discourse_enabled = models.BooleanField(default=False, blank=True)
ips4_username = models.CharField(max_length=254, blank=True, default="")
ips4_id = models.CharField(max_length=254, blank=True, default="")
smf_username = models.CharField(max_length=254, blank=True, default="")
market_username = models.CharField(max_length=254, blank=True, default="")
main_char_id = models.CharField(max_length=64, blank=True, default="") main_char_id = models.CharField(max_length=64, blank=True, default="")
user = models.ForeignKey(User) user = models.OneToOneField(User)
state = models.CharField(blank=True, null=True, choices=STATE_CHOICES, default=NONE_STATE, max_length=10) state = models.CharField(blank=True, null=True, choices=STATE_CHOICES, default=NONE_STATE, max_length=10)
def __str__(self): def __str__(self):

View File

@@ -1,6 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db.models.signals import pre_save from django.db.models.signals import pre_save, post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.contrib.auth.models import User
from authentication.models import AuthServicesInfo from authentication.models import AuthServicesInfo
from authentication.states import MEMBER_STATE, BLUE_STATE from authentication.states import MEMBER_STATE, BLUE_STATE
from authentication.tasks import make_member, make_blue, disable_member from authentication.tasks import make_member, make_blue, disable_member
@@ -22,4 +23,12 @@ def pre_save_auth_state(sender, instance, *args, **kwargs):
make_blue(instance) make_blue(instance)
else: else:
disable_member(instance.user) disable_member(instance.user)
validate_services(instance.user, instance.state) validate_services.apply(args=(instance.user,))
@receiver(post_save, sender=User)
def post_save_user(sender, instance, created, *args, **kwargs):
# ensure all users have a model
if created:
AuthServicesInfo.objects.get_or_create(user=instance)

View File

@@ -20,14 +20,35 @@ def generate_alliance_group_name(alliancename):
def disable_member(user): def disable_member(user):
"""
Disable a member who is transitioning to a NONE state.
:param user: django.contrib.auth.models.User to disable
:return:
"""
logger.debug("Disabling member %s" % user) logger.debug("Disabling member %s" % user)
if user.user_permissions.all().exists():
logger.info("Clearning user %s permission to deactivate user." % user)
user.user_permissions.clear()
if user.groups.all().exists():
logger.info("Clearing all non-public user %s groups to disable member." % user)
user.groups.remove(*user.groups.filter(authgroup__public=False))
validate_services.apply(args=(user,))
def disable_user(user):
"""
Disable a user who is being set inactive or deleted
:param user: django.contrib.auth.models.User to disable
:return:
"""
logger.debug("Disabling user %s" % user)
if user.user_permissions.all().exists(): if user.user_permissions.all().exists():
logger.info("Clearning user %s permission to deactivate user." % user) logger.info("Clearning user %s permission to deactivate user." % user)
user.user_permissions.clear() user.user_permissions.clear()
if user.groups.all().exists(): if user.groups.all().exists():
logger.info("Clearing user %s groups to deactivate user." % user) logger.info("Clearing user %s groups to deactivate user." % user)
user.groups.clear() user.groups.clear()
validate_services(user, None) validate_services.apply(args=(user,))
def make_member(auth): def make_member(auth):
@@ -63,15 +84,13 @@ def make_blue(auth):
def determine_membership_by_character(char): def determine_membership_by_character(char):
if settings.IS_CORP: if str(char.corporation_id) in settings.STR_CORP_IDS:
if int(char.corporation_id) == int(settings.CORP_ID): logger.debug("Character %s in member corp id %s" % (char, char.corporation_id))
logger.debug("Character %s in owning corp id %s" % (char, char.corporation_id)) return MEMBER_STATE
return MEMBER_STATE elif str(char.alliance_id) in settings.STR_ALLIANCE_IDS:
else: logger.debug("Character %s in member alliance id %s" % (char, char.alliance_id))
if int(char.alliance_id) == int(settings.ALLIANCE_ID): return MEMBER_STATE
logger.debug("Character %s in owning alliance id %s" % (char, char.alliance_id)) elif not EveCorporationInfo.objects.filter(corporation_id=char.corporation_id).exists():
return MEMBER_STATE
if EveCorporationInfo.objects.filter(corporation_id=char.corporation_id).exists() is False:
logger.debug("No corp model for character %s corp id %s. Unable to check standings. Non-member." % ( logger.debug("No corp model for character %s corp id %s. Unable to check standings. Non-member." % (
char, char.corporation_id)) char, char.corporation_id))
return NONE_STATE return NONE_STATE
@@ -87,7 +106,7 @@ def determine_membership_by_character(char):
def determine_membership_by_user(user): def determine_membership_by_user(user):
logger.debug("Determining membership of user %s" % user) logger.debug("Determining membership of user %s" % user)
auth, c = AuthServicesInfo.objects.get_or_create(user=user) auth = AuthServicesInfo.objects.get(user=user)
if auth.main_char_id: if auth.main_char_id:
if EveCharacter.objects.filter(character_id=auth.main_char_id).exists(): if EveCharacter.objects.filter(character_id=auth.main_char_id).exists():
char = EveCharacter.objects.get(character_id=auth.main_char_id) char = EveCharacter.objects.get(character_id=auth.main_char_id)
@@ -107,11 +126,13 @@ def set_state(user):
else: else:
state = NONE_STATE state = NONE_STATE
logger.debug("Assigning user %s to state %s" % (user, state)) logger.debug("Assigning user %s to state %s" % (user, state))
auth = AuthServicesInfo.objects.get_or_create(user=user)[0] auth = AuthServicesInfo.objects.get(user=user)
if auth.state != state: if auth.state != state:
auth.state = state auth.state = state
auth.save() auth.save()
notify(user, "Membership State Change", message="You membership state has been changed to %s" % state) notify(user, "Membership State Change", message="You membership state has been changed to %s" % state)
assign_corp_group(auth)
assign_alliance_group(auth)
def assign_corp_group(auth): def assign_corp_group(auth):

View File

@@ -1 +0,0 @@
# Create your tests here.

View File

View File

@@ -0,0 +1,84 @@
from __future__ import unicode_literals
try:
# Py3
from unittest import mock
except ImportError:
# Py2
import mock
from django.test import TestCase
from django.contrib.auth.models import Group, Permission
from alliance_auth.tests.auth_utils import AuthUtils
from authentication.tasks import disable_member, disable_user
class AuthenticationTasksTestCase(TestCase):
def setUp(self):
self.member = AuthUtils.create_member('auth_member')
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
@mock.patch('services.signals.transaction')
def test_disable_member(self, transaction):
# Inert signals action
transaction.on_commit.side_effect = lambda fn: fn()
# Add permission
perm = Permission.objects.create(codename='test_perm', name='test perm', content_type_id=1)
# Add public group
pub_group = Group.objects.create(name="A Public group")
pub_group.authgroup.internal = False
pub_group.authgroup.public = True
pub_group.save()
# Setup member
self.member.user_permissions.add(perm)
self.member.groups.add(pub_group)
# Pre assertion
self.assertIn(pub_group, self.member.groups.all())
self.assertGreater(len(self.member.groups.all()), 1)
# Act
disable_member(self.member)
# Assert
self.assertIn(pub_group, self.member.groups.all())
# Everything but the single public group wiped
self.assertEqual(len(self.member.groups.all()), 1)
# All permissions wiped
self.assertEqual(len(self.member.user_permissions.all()), 0)
@mock.patch('services.signals.transaction')
def test_disable_user(self, transaction):
# Inert signals action
transaction.on_commit.side_effect = lambda fn: fn()
# Add permission
perm = Permission.objects.create(codename='test_perm', name='test perm', content_type_id=1)
# Add public group
pub_group = Group.objects.create(name="A Public group")
pub_group.authgroup.internal = False
pub_group.authgroup.public = True
pub_group.save()
# Setup member
self.member.user_permissions.add(perm)
self.member.groups.add(pub_group)
# Pre assertion
self.assertIn(pub_group, self.member.groups.all())
self.assertGreater(len(self.member.groups.all()), 1)
# Act
disable_user(self.member)
# Assert
# All groups wiped
self.assertEqual(len(self.member.groups.all()), 0)
# All permissions wiped
self.assertEqual(len(self.member.user_permissions.all()), 0)

View File

@@ -4,13 +4,14 @@ from django.contrib.auth import logout
from django.contrib.auth import authenticate from django.contrib.auth import authenticate
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext_lazy as _
from eveonline.managers import EveManager from eveonline.managers import EveManager
from eveonline.models import EveCharacter from eveonline.models import EveCharacter
from authentication.models import AuthServicesInfo from authentication.models import AuthServicesInfo
from authentication.forms import LoginForm, RegistrationForm from authentication.forms import LoginForm, RegistrationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib import messages from django.contrib import messages
from eve_sso.decorators import token_required from esi.decorators import token_required
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -34,10 +35,10 @@ def login_user(request):
return redirect(redirect_to) return redirect(redirect_to)
else: else:
logger.info("Login attempt failed for user %s: user marked inactive." % user) logger.info("Login attempt failed for user %s: user marked inactive." % user)
messages.warning(request, 'Your account has been disabled.') messages.warning(request, _('Your account has been disabled.'))
else: else:
logger.info("Failed login attempt: provided username %s" % form.cleaned_data['username']) logger.info("Failed login attempt: provided username %s" % form.cleaned_data['username'])
messages.error(request, 'Username/password invalid.') messages.error(request, _('Username/password invalid.'))
return render(request, 'public/login.html', context={'form': form}) return render(request, 'public/login.html', context={'form': form})
else: else:
logger.debug("Providing new login form.") logger.debug("Providing new login form.")
@@ -67,7 +68,8 @@ def register_user_view(request):
user.save() user.save()
logger.info("Created new user %s" % user) logger.info("Created new user %s" % user)
messages.warning(request, 'Add an API key to set up your account.') login(request, user)
messages.warning(request, _('Add an API key to set up your account.'))
return redirect("auth_dashboard") return redirect("auth_dashboard")
else: else:
@@ -88,32 +90,27 @@ def index_view(request):
return render(request, 'public/index.html') return render(request, 'public/index.html')
@login_required
def dashboard_view(request):
logger.debug("dashboard_view called by user %s" % request.user)
render_items = {'characters': EveManager.get_characters_by_owner_id(request.user.id),
'authinfo': AuthServicesInfo.objects.get_or_create(user=request.user)[0]}
return render(request, 'registered/dashboard.html', context=render_items)
@login_required @login_required
def help_view(request): def help_view(request):
logger.debug("help_view called by user %s" % request.user) logger.debug("help_view called by user %s" % request.user)
return render(request, 'registered/help.html') return render(request, 'registered/help.html')
@token_required(new=True) @token_required(new=True)
def sso_login(request, tokens=[]): def sso_login(request, token):
token = tokens[0]
try: try:
char = EveCharacter.objects.get(character_id=token.character_id) char = EveCharacter.objects.get(character_id=token.character_id)
if char.user: if char.user:
if char.user.is_active: if char.user.is_active:
login(request, char.user) login(request, char.user)
return redirect(dashboard_view) token.user = char.user
token.save()
return redirect('auth_dashboard')
else: else:
messages.error(request, 'Your account has been disabled.') messages.error(request, _('Your account has been disabled.'))
else: else:
messages.warning(request, 'Authenticated character has no owning account. Please log in with username and password.') messages.warning(request,
_('Authenticated character has no owning account. Please log in with username and password.'))
except EveCharacter.DoesNotExist: except EveCharacter.DoesNotExist:
messages.error(request, 'No account exists with the authenticated character. Please create an account first.') messages.error(request, _('No account exists with the authenticated character. Please create an account first.'))
return redirect(login_user) return redirect(login_user)

View File

@@ -1 +1,5 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from corputils.models import CorpStats
from django.contrib import admin
admin.site.register(CorpStats)

View File

@@ -1,8 +0,0 @@
from __future__ import unicode_literals
from django import forms
from django.utils.translation import ugettext_lazy as _
class CorputilsSearchForm(forms.Form):
search_string = forms.CharField(max_length=254, required=True, label="",
widget=forms.TextInput(attrs={'placeholder': _('Search characters...')}))

42
corputils/managers.py Normal file
View File

@@ -0,0 +1,42 @@
from django.db import models
from authentication.models import AuthServicesInfo
from eveonline.models import EveCharacter
class CorpStatsQuerySet(models.QuerySet):
def visible_to(self, user):
# superusers get all visible
if user.is_superuser:
return self
auth = AuthServicesInfo.objects.get(user=user)
try:
char = EveCharacter.objects.get(character_id=auth.main_char_id)
# build all accepted queries
queries = []
if user.has_perm('corputils.view_corp_corpstats'):
queries.append(models.Q(corp__corporation_id=char.corporation_id))
if user.has_perm('corputils.view_alliance_corpstats'):
queries.append(models.Q(corp__alliance__alliance_id=char.alliance_id))
if user.has_perm('corputils.view_blue_corpstats'):
queries.append(models.Q(corp__is_blue=True))
# filter based on queries
if queries:
query = queries.pop()
for q in queries:
query |= q
return self.filter(query)
else:
# not allowed to see any
return self.none()
except EveCharacter.DoesNotExist:
return self.none()
class CorpStatsManager(models.Manager):
def get_queryset(self):
return CorpStatsQuerySet(self.model, using=self._db)
def visible_to(self, user):
return self.get_queryset().visible_to(user)

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-14 21:36
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('esi', '0002_scopes_20161208'),
('eveonline', '0004_eveapikeypair_sso_verified'),
]
operations = [
migrations.CreateModel(
name='CorpStats',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('last_update', models.DateTimeField(auto_now=True)),
('_members', models.TextField(default='{}')),
('corp', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='eveonline.EveCorporationInfo')),
('token', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='esi.Token')),
],
options={
'default_permissions': ('add', 'change', 'remove', 'view_corp', 'view_alliance', 'view_blue'),
'verbose_name': 'corp stats',
'verbose_name_plural': 'corp stats',
'permissions': (('corp_apis', 'Can view API keys of members of their corporation.'), ('alliance_apis', 'Can view API keys of members of their alliance.'), ('blue_apis', 'Can view API keys of members of blue corporations.')),
},
),
]

View File

@@ -0,0 +1,125 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-14 21:48
from __future__ import unicode_literals
from django.db import migrations
PERMISSIONS = {
'user': [
'corp_apis',
'alliance_apis',
],
'corpstats': {
'corp_apis': 'Can view API keys of members of their corporation.',
'alliance_apis': 'Can view API keys of members of their alliance.',
'blue_apis': 'Can view API keys of members of blue corporations.',
'view_corp_corpstats': 'Can view corp stats of their corporation.',
'view_alliance_corpstats': 'Can view corp stats of members of their alliance.',
'view_blue_corpstats': 'Can view corp stats of blue corporations.',
}
}
def user_permissions_dict(apps):
Permission = apps.get_model('auth', 'Permission')
ContentType = apps.get_model('contenttypes', 'ContentType')
User = apps.get_model('auth', 'User')
CorpStats = apps.get_model('corputils', 'CorpStats')
user_ct = ContentType.objects.get_for_model(User)
corpstats_ct = ContentType.objects.get_for_model(CorpStats)
return {
'user': {x: Permission.objects.get_or_create(name=x, codename=x, content_type=user_ct)[0] for x in PERMISSIONS['user']},
'corpstats': {x: Permission.objects.get_or_create(codename=x, name=y, content_type=corpstats_ct)[0] for x, y in PERMISSIONS['corpstats'].items()},
}
def users_with_permission(apps, perm):
User = apps.get_model('auth', 'User')
return User.objects.filter(user_permissions=perm.pk)
def groups_with_permission(apps, perm):
Group = apps.get_model('auth', 'Group')
return Group.objects.filter(permissions=perm.pk)
def forward(apps, schema_editor):
perm_dict = user_permissions_dict(apps)
corp_users = users_with_permission(apps, perm_dict['user']['corp_apis'])
for u in corp_users:
u.user_permissions.add(perm_dict['corpstats']['corp_apis'].pk)
u.user_permissions.add(perm_dict['corpstats']['view_corp_corpstats'].pk)
alliance_users = users_with_permission(apps, perm_dict['user']['alliance_apis'])
for u in alliance_users:
u.user_permissions.add(perm_dict['corpstats']['alliance_apis'].pk)
u.user_permissions.add(perm_dict['corpstats']['view_alliance_corpstats'].pk)
corp_groups = groups_with_permission(apps, perm_dict['user']['corp_apis'])
for g in corp_groups:
g.permissions.add(perm_dict['corpstats']['corp_apis'].pk)
g.permissions.add(perm_dict['corpstats']['view_corp_corpstats'].pk)
alliance_groups = groups_with_permission(apps, perm_dict['user']['alliance_apis'])
for g in alliance_groups:
g.permissions.add(perm_dict['corpstats']['alliance_apis'].pk)
g.permissions.add(perm_dict['corpstats']['view_alliance_corpstats'].pk)
for name, perm in perm_dict['user'].items():
perm.delete()
def reverse(apps, schema_editor):
perm_dict = user_permissions_dict(apps)
corp_users = users_with_permission(apps, perm_dict['corpstats']['view_corp_corpstats'])
corp_api_users = users_with_permission(apps, perm_dict['corpstats']['corp_apis'])
corp_us = corp_users | corp_api_users
for u in corp_us.distinct():
u.user_permissions.add(perm_dict['user']['corp_apis'].pk)
for u in corp_users:
u.user_permissions.remove(perm_dict['corpstats']['view_corp_corpstats'].pk)
for u in corp_api_users:
u.user_permissions.remove(perm_dict['corpstats']['corp_apis'].pk)
alliance_users = users_with_permission(apps, perm_dict['corpstats']['view_alliance_corpstats'])
alliance_api_users = users_with_permission(apps, perm_dict['corpstats']['alliance_apis'])
alliance_us = alliance_users | alliance_api_users
for u in alliance_us.distinct():
u.user_permissions.add(perm_dict['user']['alliance_apis'].pk)
for u in alliance_users:
u.user_permissions.remove(perm_dict['corpstats']['view_alliance_corpstats'].pk)
for u in alliance_api_users:
u.user_permissions.remove(perm_dict['corpstats']['alliance_apis'].pk)
corp_groups = groups_with_permission(apps, perm_dict['corpstats']['view_corp_corpstats'])
corp_api_groups = groups_with_permission(apps, perm_dict['corpstats']['corp_apis'])
corp_gs = corp_groups | corp_api_groups
for g in corp_groups.distinct():
g.permissions.add(perm_dict['user']['corp_apis'].pk)
for g in corp_groups:
g.permissions.remove(perm_dict['corpstats']['view_corp_corpstats'].pk)
for g in corp_api_groups:
g.permissions.remove(perm_dict['corpstats']['corp_apis'].pk)
alliance_groups = groups_with_permission(apps, perm_dict['corpstats']['view_alliance_corpstats'])
alliance_api_groups = groups_with_permission(apps, perm_dict['corpstats']['alliance_apis'])
alliance_gs = alliance_groups | alliance_api_groups
for g in alliance_gs.distinct():
g.permissions.add(perm_dict['user']['alliance_apis'].pk)
for g in alliance_groups:
g.permissions.remove(perm_dict['corpstats']['view_alliance_corpstats'].pk)
for g in alliance_api_groups:
g.permissions.remove(perm_dict['corpstats']['alliance_apis'].pk)
class Migration(migrations.Migration):
dependencies = [
('corputils', '0001_initial'),
('authentication', '0005_delete_perms'),
('auth', '0008_alter_user_username_max_length'),
]
operations = [
migrations.RunPython(forward, reverse),
]

View File

View File

@@ -1 +1,203 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.utils.encoding import python_2_unicode_compatible
from django.db import models
from eveonline.models import EveCorporationInfo, EveCharacter, EveApiKeyPair
from esi.models import Token
from esi.errors import TokenError
from notifications import notify
from authentication.models import AuthServicesInfo
from bravado.exception import HTTPForbidden
from corputils.managers import CorpStatsManager
from operator import attrgetter
import json
import logging
import os
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
logger = logging.getLogger(__name__)
@python_2_unicode_compatible
class CorpStats(models.Model):
token = models.ForeignKey(Token, on_delete=models.CASCADE)
corp = models.OneToOneField(EveCorporationInfo)
last_update = models.DateTimeField(auto_now=True)
_members = models.TextField(default='{}')
class Meta:
permissions = (
('corp_apis', 'Can view API keys of members of their corporation.'),
('alliance_apis', 'Can view API keys of members of their alliance.'),
('blue_apis', 'Can view API keys of members of blue corporations.'),
('view_corp_corpstats', 'Can view corp stats of their corporation.'),
('view_alliance_corpstats', 'Can view corp stats of members of their alliance.'),
('view_blue_corpstats', 'Can view corp stats of blue corporations.'),
)
verbose_name = "corp stats"
verbose_name_plural = "corp stats"
objects = CorpStatsManager()
def __str__(self):
return "%s for %s" % (self.__class__.__name__, self.corp)
def update(self):
try:
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
'corporation_id'] == int(self.corp.corporation_id)
members = c.Corporation.get_corporations_corporation_id_members(
corporation_id=self.corp.corporation_id).result()
member_ids = [m['character_id'] for m in members]
# requesting too many ids per call results in a HTTP400
# the swagger spec doesn't have a maxItems count
# manual testing says we can do over 350, but let's not risk it
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
member_name_chunks = [c.Character.get_characters_names(character_ids=id_chunk).result() for id_chunk in
member_id_chunks]
member_list = {}
for name_chunk in member_name_chunks:
member_list.update({m['character_id']: m['character_name'] for m in name_chunk})
self.members = member_list
self.save()
except TokenError as e:
logger.warning("%s failed to update: %s" % (self, e))
if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self,
message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.",
level="error")
self.delete()
except HTTPForbidden as e:
logger.warning("%s failed to update: %s" % (self, e))
if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self,
message="%s: %s" % (e.status_code, e.message), level="error")
self.delete()
except AssertionError:
logger.warning("%s token character no longer in corp." % self)
if self.token.user:
notify(self.token.user, "%s cannot update with your ESI token." % self,
message="%s cannot update with your ESI token as you have left corp." % self, level="error")
self.delete()
@property
def members(self):
return json.loads(self._members)
@members.setter
def members(self, dict):
self._members = json.dumps(dict)
@property
def member_ids(self):
return [id for id, name in self.members.items()]
@property
def member_names(self):
return [name for id, name in self.members.items()]
def show_apis(self, user):
auth = AuthServicesInfo.objects.get(user=user)
if auth.main_char_id:
try:
char = EveCharacter.objects.get(character_id=auth.main_char_id)
if char.corporation_id == self.corp.corporation_id and user.has_perm('corputils.corp_apis'):
return True
if self.corp.alliance and char.alliance_id == self.corp.alliance.alliance_id and user.has_perm(
'corputils.alliance_apis'):
return True
if user.has_perm('corputils.blue_apis') and self.corp.is_blue:
return True
except EveCharacter.DoesNotExist:
pass
return user.is_superuser
def entered_apis(self):
return EveCharacter.objects.filter(character_id__in=self.member_ids).exclude(api_id__isnull=True).count()
def member_count(self):
return len(self.members)
def user_count(self, members):
mainchars = []
for member in members:
if hasattr(member.main, 'character_name'):
mainchars.append(member.main.character_name)
return len(set(mainchars))
@python_2_unicode_compatible
class MemberObject(object):
def __init__(self, character_id, character_name, show_apis=False):
self.character_id = character_id
self.character_name = character_name
try:
char = EveCharacter.objects.get(character_id=character_id)
auth = AuthServicesInfo.objects.get(user=char.user)
try:
self.main = EveCharacter.objects.get(character_id=auth.main_char_id)
self.main_user = self.main.character_name
except EveCharacter.DoesNotExist:
self.main = None
self.main_user = ''
api = EveApiKeyPair.objects.get(api_id=char.api_id)
self.registered = True
if show_apis:
self.api = api
else:
self.api = None
except (EveCharacter.DoesNotExist, AuthServicesInfo.DoesNotExist):
self.main = None
self.api = None
self.registered = False
self.main_user = ''
except EveApiKeyPair.DoesNotExist:
self.api = None
self.registered = False
self.main_user = ''
def __str__(self):
return self.character_name
def portrait_url(self, size=32):
return "https://image.eveonline.com/Character/%s_%s.jpg" % (self.character_id, size)
def get_member_objects(self, user):
show_apis = self.show_apis(user)
member_list = [CorpStats.MemberObject(id, name, show_apis=show_apis) for id, name in self.members.items()]
outlist = sorted([m for m in member_list if m.main_user], key=attrgetter('main_user', 'character_name'))
outlist = outlist + sorted([m for m in member_list if not m.main_user], key=attrgetter('character_name'))
return outlist
def can_update(self, user):
return user.is_superuser or user == self.token.user
@python_2_unicode_compatible
class ViewModel(object):
def __init__(self, corpstats, user):
self.corp = corpstats.corp
self.members = corpstats.get_member_objects(user)
self.can_update = corpstats.can_update(user)
self.total_members = len(self.members)
self.total_users = corpstats.user_count(self.members)
self.registered_members = corpstats.entered_apis()
self.show_apis = corpstats.show_apis(user)
self.last_updated = corpstats.last_update
def __str__(self):
return str(self.corp)
def corp_logo(self, size=128):
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corp.corporation_id, size)
def alliance_logo(self, size=128):
if self.corp.alliance:
return "https://image.eveonline.com/Alliance/%s_%s.png" % (self.corp.alliance.alliance_id, size)
else:
return "https://image.eveonline.com/Alliance/1_%s.png" % size
def get_view_model(self, user):
return CorpStats.ViewModel(self, user)

1
corputils/swagger.json Normal file

File diff suppressed because one or more lines are too long

14
corputils/tasks.py Normal file
View File

@@ -0,0 +1,14 @@
from corputils.models import CorpStats
from alliance_auth.celeryapp import app
@app.task
def update_corpstats(pk):
cs = CorpStats.objects.get(pk=pk)
cs.update()
@app.task
def update_all_corpstats():
for cs in CorpStats.objects.all():
update_corpstats.delay(cs.pk)

View File

@@ -0,0 +1,39 @@
{% extends 'public/base.html' %}
{% load i18n %}
{% block title %}{% trans "Corporation Member Data" %}{% endblock %}
{% block page_title %}{% trans "Corporation Member Data" %}{% endblock %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Corporation Member Data" %}</h1>
<div class="col-lg-10 col-lg-offset-1 container">
<nav class="navbar navbar-default">
<div class="container-fluid">
<ul class="nav navbar-nav">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Corporations" %}<span class="caret"></span></a>
<ul class="dropdown-menu">
{% for corpstat in available %}
<li>
<a href="{% url 'corputils:view_corp' corpstat.corp.corporation_id %}">{{ corpstat.corp.corporation_name }}</a>
</li>
{% endfor %}
</ul>
</li>
{% if perms.corputils.add_corpstats %}
<li>
<a href="{% url 'corputils:add' %}">{% trans "Add" %}</a>
</li>
{% endif %}
</ul>
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
<div class="form-group">
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}{% trans "Search characters..." %}{% endif %}">
</div>
</form>
</div>
</nav>
{% block member_data %}{% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,93 @@
{% extends 'corputils/base.html' %}
{% load i18n %}
{% load humanize %}
{% load bootstrap_pagination %}
{% load eveonline_extras %}
{% block member_data %}
{% if corpstats %}
<div class="row">
<div class="col-lg-12 text-center">
<table class="table">
<tr>
<td class="text-center col-lg-6 {% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}"><img class="ra-avatar" src="{{ corpstats.corp_logo }}"></td>
{% if corpstats.corp.alliance %}
<td class="text-center col-lg-6"><img class="ra-avatar" src="{{ corpstats.alliance_logo }}"></td>
{% endif %}
</tr>
<tr>
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
{% if corpstats.corp.alliance %}
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
{% endif %}
</tr>
</table>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<b>{% trans "API Index: " %}</b> {{ corpstats.total_users }} Main Character{{ corpstats.total_users|pluralize }}
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="{{ corpstats.registered_members }}" aria-valuemin="0" aria-valuemax="{{ corpstats.total_members }}" style="width: {% widthratio corpstats.registered_members corpstats.total_members 100 %}%;">
{{ corpstats.registered_members }}/{{ corpstats.total_members }}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading clearfix">
<div class="panel-title pull-left">
<h4>{% trans "Members" %}</h4>
</div>
<div class="panel-title pull-right">
{% trans "Last update:" %} {{ corpstats.last_updated|naturaltime }}
{% if corpstats.can_update %}
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
<span class="glyphicon glyphicon-refresh"></span>
</a>
{% endif %}
</div>
</div>
<div class="panel-body">
<div class="text-center">
{% bootstrap_paginate members range=10 %}
</div>
<div class="table-responsive">
<table class="table table-hover">
<tr>
<th></th>
<th class="text-center">{% trans "Character" %}</th>
{% if corpstats.show_apis %}
<th class="text-center">API</th>
{% endif %}
<th class="text-center">{% trans "zKillboard" %}</th>
<th class="text-center">{% trans "Main Character" %}</th>
<th class="text-center">{% trans "Main Corporation" %}</th>
<th class="text-center">{% trans "Main Alliance" %}</th>
</tr>
{% for member in members %}
<tr {% if not member.registered %}class="danger"{% endif %}>
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member.character_name }}</td>
{% if corpstats.show_apis %}
{% if member.api %}
<td class="text-center">{{ member.api|api_link:'label label-primary' }}</td>
{% else %}
<td></td>
{% endif %}
{% endif %}
<td class="text-center"><a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a></td>
<td class="text-center">{{ member.main.character_name }}</td>
<td class="text-center">{{ member.main.corporation_name }}</td>
<td class="text-center">{{ member.main.alliance_name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,44 @@
{% extends "corputils/base.html" %}
{% load i18n %}
{% load bootstrap_pagination %}
{% load eveonline_extras %}
{% block member_data %}
<div class="panel panel-default">
<div class="panel-heading clearfix">
<div class="panel-title pull-left">{% trans "Search Results" %}</div>
</div>
<div class="panel-body">
<div class="text-center">
{% bootstrap_paginate results range=10 %}
</div>
<table class="table table-hover">
<tr>
<th class="text-center"></th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "Corporation" %}</th>
<th class="text-center">{% trans "API" %}</th>
<th class="text-center">{% trans "zKillboard" %}</th>
<th class="text-center">{% trans "Main Character" %}</th>
<th class="text-center">{% trans "Main Corporation" %}</th>
<th class="text-center">{% trans "Main Alliance" %}</th>
</tr>
{% for result in results %}
<tr {% if not result.1.registered %}class="danger"{% endif %}>
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ result.1.character_name }}</td>
<td class="text-center">{{ result.0.corp.corporation_name }}</td>
{% if result.1.api %}
<td class="text-center">{{ result.1.api|api_link:"label label-primary" }}</td>
{% else %}
<td></td>
{% endif %}
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a></td>
<td class="text-center">{{ result.1.main.character_name }}</td>
<td class="text-center">{{ result.1.main.corporation_name }}</td>
<td class="text-center">{{ result.1.main.alliance_name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock %}

11
corputils/urls.py Normal file
View File

@@ -0,0 +1,11 @@
from django.conf.urls import url
import corputils.views
app_name = 'corputils'
urlpatterns = [
url(r'^$', corputils.views.corpstats_view, name='view'),
url(r'^add/$', corputils.views.corpstats_add, name='add'),
url(r'^(?P<corp_id>(\d)*)/$', corputils.views.corpstats_view, name='view_corp'),
url(r'^(?P<corp_id>(\d)+)/update/$', corputils.views.corpstats_update, name='update'),
url(r'^search/$', corputils.views.corpstats_search, name='search'),
]

View File

@@ -1,325 +1,148 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.conf import settings from django.conf import settings
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
from django.shortcuts import render, redirect from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db import IntegrityError
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from eveonline.models import EveCharacter, EveCorporationInfo
from corputils.models import CorpStats
from esi.decorators import token_required
from bravado.exception import HTTPError
import os
from collections import namedtuple SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
from authentication.models import AuthServicesInfo MEMBERS_PER_PAGE = int(getattr(settings, 'CORPSTATS_MEMBERS_PER_PAGE', 20))
from services.managers.eve_api_manager import EveApiManager
from services.managers.evewho_manager import EveWhoManager
from eveonline.models import EveCorporationInfo
from eveonline.models import EveAllianceInfo
from eveonline.models import EveCharacter
from eveonline.models import EveApiKeyPair
from fleetactivitytracking.models import Fat
from corputils.forms import CorputilsSearchForm
from evelink.api import APIError
import logging
import datetime
logger = logging.getLogger(__name__)
class Player(object): def get_page(model_list, page_num):
def __init__(self, main, user, maincorp, maincorpid, altlist, apilist, n_fats): p = Paginator(model_list, MEMBERS_PER_PAGE)
self.main = main try:
self.user = user members = p.page(page_num)
self.maincorp = maincorp except PageNotAnInteger:
self.maincorpid = maincorpid members = p.page(1)
self.altlist = altlist except EmptyPage:
self.apilist = apilist members = p.page(p.num_pages)
self.n_fats = n_fats return members
def first_day_of_next_month(year, month): def access_corpstats_test(user):
if month == 12: return user.has_perm('corputils.view_corp_corpstats') or user.has_perm(
return datetime.datetime(year + 1, 1, 1) 'corputils.view_alliance_corpstats') or user.has_perm('corputils.view_blue_corpstats')
else:
return datetime.datetime(year, month + 1, 1)
def first_day_of_previous_month(year, month):
if month == 1:
return datetime.datetime(year - 1, 12, 1)
else:
return datetime.datetime(year, month - 1, 1)
@login_required @login_required
def corp_member_view(request, corpid=None, year=datetime.date.today().year, month=datetime.date.today().month): @user_passes_test(access_corpstats_test)
year = int(year) @permission_required('corputils.add_corpstats')
month = int(month) @token_required(scopes='esi-corporations.read_corporation_membership.v1')
start_of_month = datetime.datetime(year, month, 1) def corpstats_add(request, token):
start_of_next_month = first_day_of_next_month(year, month)
start_of_previous_month = first_day_of_previous_month(year, month)
logger.debug("corp_member_view called by user %s" % request.user)
try: try:
user_main = EveCharacter.objects.get( if EveCharacter.objects.filter(character_id=token.character_id).exists():
character_id=AuthServicesInfo.objects.get_or_create(user=request.user)[0].main_char_id) corp_id = EveCharacter.objects.get(character_id=token.character_id).corporation_id
user_corp_id = user_main.corporation_id
except (ValueError, EveCharacter.DoesNotExist):
user_corp_id = settings.CORP_ID
if not settings.IS_CORP:
alliance = EveAllianceInfo.objects.get(alliance_id=settings.ALLIANCE_ID)
alliancecorps = EveCorporationInfo.objects.filter(alliance=alliance)
membercorplist = [(int(membercorp.corporation_id), str(membercorp.corporation_name)) for membercorp in
alliancecorps]
membercorplist.sort(key=lambda tup: tup[1])
membercorp_id_list = [int(membercorp.corporation_id) for membercorp in alliancecorps]
bluecorps = EveCorporationInfo.objects.filter(is_blue=True)
bluecorplist = [(int(bluecorp.corporation_id), str(bluecorp.corporation_name)) for bluecorp in bluecorps]
bluecorplist.sort(key=lambda tup: tup[1])
bluecorp_id_list = [int(bluecorp.corporation_id) for bluecorp in bluecorps]
if not (user_corp_id in membercorp_id_list or user_corp_id not in bluecorp_id_list):
user_corp_id = None
if not corpid:
if settings.IS_CORP:
corpid = settings.CORP_ID
elif user_corp_id:
corpid = user_corp_id
else: else:
corpid = membercorplist[0][0] corp_id = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH).Character.get_characters_character_id(
character_id=token.character_id).result()['corporation_id']
corp = EveCorporationInfo.objects.get(corporation_id=corpid) corp = EveCorporationInfo.objects.get(corporation_id=corp_id)
if request.user.has_perm('auth.alliance_apis') or (request.user.has_perm('auth.corp_apis') and user_corp_id == corpid): cs = CorpStats.objects.create(token=token, corp=corp)
logger.debug("Retreiving and sending API-information") try:
cs.update()
if settings.IS_CORP: except HTTPError as e:
try: messages.error(request, str(e))
member_list = EveApiManager.get_corp_membertracking(settings.CORP_API_ID, settings.CORP_API_VCODE) assert cs.pk # ensure update was successful
except APIError: if CorpStats.objects.filter(pk=cs.pk).visible_to(request.user).exists():
logger.debug("Corp API does not have membertracking scope, using EveWho data instead.") return redirect('corputils:view_corp', corp_id=corp.corporation_id)
member_list = EveWhoManager.get_corporation_members(corpid) except EveCorporationInfo.DoesNotExist:
else: messages.error(request, _('Unrecognized corporation. Please ensure it is a member of the alliance or a blue.'))
member_list = EveWhoManager.get_corporation_members(corpid) except IntegrityError:
messages.error(request, _('Selected corp already has a statistics module.'))
characters_with_api = {} except AssertionError:
characters_without_api = {} messages.error(request, _('Failed to gather corporation statistics with selected token.'))
return redirect('corputils:view')
num_registered_characters = 0
for char_id, member_data in member_list.items():
try:
char = EveCharacter.objects.get(character_id=char_id)
char_owner = char.user
try:
if not char_owner:
raise AttributeError("Character has no assigned user.")
mainid = int(AuthServicesInfo.objects.get_or_create(user=char_owner)[0].main_char_id)
mainchar = EveCharacter.objects.get(character_id=mainid)
mainname = mainchar.character_name
maincorp = mainchar.corporation_name
maincorpid = mainchar.corporation_id
api_pair = EveApiKeyPair.objects.get(api_id=char.api_id)
except (ValueError, EveCharacter.DoesNotExist, EveApiKeyPair.DoesNotExist):
logger.debug("No main character seem to be set for character %s" % char.character_name)
mainname = "User: " + char_owner.username
mainchar = char
maincorp = "Not set."
maincorpid = None
api_pair = None
except AttributeError:
logger.debug("No associated user for character %s" % char.character_name)
mainname = None
mainchar = char
maincorp = None
maincorpid = None
try:
api_pair = EveApiKeyPair.objects.get(api_id=char.api_id)
except EveApiKeyPair.DoesNotExist:
api_pair = None
num_registered_characters += 1
characters_with_api.setdefault(mainname, Player(main=mainchar,
user=char_owner,
maincorp=maincorp,
maincorpid=maincorpid,
altlist=[],
apilist=[],
n_fats=0)
).altlist.append(char)
if api_pair:
characters_with_api[mainname].apilist.append(api_pair)
except EveCharacter.DoesNotExist:
characters_without_api.update({member_data["name"]: member_data["id"]})
for char in EveCharacter.objects.filter(corporation_id=corpid):
if not int(char.character_id) in member_list:
logger.debug("Character '%s' does not exist in EveWho dump." % char.character_name)
char_owner = char.user
try:
if not char_owner:
raise AttributeError("Character has no assigned user.")
mainid = int(AuthServicesInfo.objects.get_or_create(user=char_owner)[0].main_char_id)
mainchar = EveCharacter.objects.get(character_id=mainid)
mainname = mainchar.character_name
maincorp = mainchar.corporation_name
maincorpid = mainchar.corporation_id
api_pair = EveApiKeyPair.objects.get(api_id=char.api_id)
except (ValueError, EveCharacter.DoesNotExist, EveApiKeyPair.DoesNotExist):
logger.debug("No main character seem to be set for character %s" % char.character_name)
mainname = "User: " + char_owner.username
mainchar = char
maincorp = "Not set."
maincorpid = None
api_pair = None
except AttributeError:
logger.debug("No associated user for character %s" % char.character_name)
mainname = None
mainchar = char
maincorp = None
maincorpid = None
try:
api_pair = EveApiKeyPair.objects.get(api_id=char.api_id)
except EveApiKeyPair.DoesNotExist:
api_pair = None
num_registered_characters += 1
characters_with_api.setdefault(mainname, Player(main=mainchar,
user=char_owner,
maincorp=maincorp,
maincorpid=maincorpid,
altlist=[],
apilist=[],
n_fats=0)
).altlist.append(char)
if api_pair:
characters_with_api[mainname].apilist.append(api_pair)
n_unacounted = corp.member_count - (num_registered_characters + len(characters_without_api))
for mainname, player in characters_with_api.items():
fats_this_month = Fat.objects.filter(user=player.user).filter(
fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lt=start_of_next_month)
characters_with_api[mainname].n_fats = len(fats_this_month)
if start_of_next_month > datetime.datetime.now():
start_of_next_month = None
if not settings.IS_CORP:
context = {"membercorplist": membercorplist,
"corp": corp,
"characters_with_api": sorted(characters_with_api.items()),
'n_registered': num_registered_characters,
'n_unacounted': n_unacounted,
"characters_without_api": sorted(characters_without_api.items()),
"search_form": CorputilsSearchForm()}
else:
logger.debug("corp_member_view running in corportation mode")
context = {"corp": corp,
"characters_with_api": sorted(characters_with_api.items()),
'n_registered': num_registered_characters,
'n_unacounted': n_unacounted,
"characters_without_api": sorted(characters_without_api.items()),
"search_form": CorputilsSearchForm()}
context["next_month"] = start_of_next_month
context["previous_month"] = start_of_previous_month
context["this_month"] = start_of_month
return render(request, 'registered/corputils.html', context=context)
else:
logger.warn('User %s (%s) not authorized to view corp stats for corp id %s' % (request.user, user_corp_id, corpid))
return redirect("auth_dashboard")
def can_see_api(user, character):
if user.has_perm('auth.alliance_apis'):
return True
try:
user_main = EveCharacter.objects.get(
character_id=AuthServicesInfo.objects.get_or_create(user=user)[0].main_char_id)
if user.has_perm('auth.corp_apis') and user_main.corporation_id == character.corporation_id:
return True
except EveCharacter.DoesNotExist:
return False
return False
@login_required @login_required
def corputils_search(request, corpid=settings.CORP_ID): @user_passes_test(access_corpstats_test)
logger.debug("corputils_search called by user %s" % request.user) def corpstats_view(request, corp_id=None):
corpstats = None
corp = EveCorporationInfo.objects.get(corporation_id=corpid) # get requested model
if corp_id:
corp = get_object_or_404(EveCorporationInfo, corporation_id=corp_id)
corpstats = get_object_or_404(CorpStats, corp=corp)
authorized = False # get available models
try: available = CorpStats.objects.visible_to(request.user)
user_main = EveCharacter.objects.get(
character_id=AuthServicesInfo.objects.get_or_create(user=request.user)[0].main_char_id)
if request.user.has_perm('auth.alliance_apis') or (
request.user.has_perm('auth.corp_apis') and (user_main.corporation_id == corpid)):
logger.debug("Retreiving and sending API-information")
authorized = True
except (ValueError, EveCharacter.DoesNotExist):
if request.user.has_perm('auth.alliance_apis'):
logger.debug("Retrieving and sending API-information")
authorized = True
if authorized: # ensure we can see the requested model
if request.method == 'POST': if corpstats and corpstats not in available:
form = CorputilsSearchForm(request.POST) raise PermissionDenied('You do not have permission to view the selected corporation statistics module.')
logger.debug("Request type POST contains form valid: %s" % form.is_valid())
if form.is_valid():
# Really dumb search and only checks character name
# This can be improved but it does the job for now
searchstring = form.cleaned_data['search_string']
logger.debug("Searching for player with character name %s for user %s" % (searchstring, request.user))
member_list = {} # get default model if none requested
if settings.IS_CORP: if not corp_id and available.count() == 1:
member_list = EveApiManager.get_corp_membertracking(settings.CORP_API_ID, settings.CORP_API_VCODE) corpstats = available[0]
if not member_list:
logger.debug('Unable to fetch members from API. Pulling from EveWho')
member_list = EveWhoManager.get_corporation_members(corpid)
SearchResult = namedtuple('SearchResult', context = {
['name', 'id', 'main', 'api_registered', 'character', 'apiinfo']) 'available': available,
}
searchresults = [] # paginate
for memberid, member_data in member_list.items(): members = []
if searchstring.lower() in member_data["name"].lower(): if corpstats:
try: page = request.GET.get('page', 1)
char = EveCharacter.objects.get(character_name=member_data["name"]) members = get_page(corpstats.get_member_objects(request.user), page)
user = char.user
mainid = int(AuthServicesInfo.objects.get_or_create(user=user)[0].main_char_id)
main = EveCharacter.objects.get(character_id=mainid)
if can_see_api(request.user, char):
api_registered = True
apiinfo = EveApiKeyPair.objects.get(api_id=char.api_id)
else:
api_registered = False
apiinfo = None
except EveCharacter.DoesNotExist:
api_registered = False
char = None
main = ""
apiinfo = None
searchresults.append(SearchResult(name=member_data["name"], id=memberid, main=main, if corpstats:
api_registered=api_registered, context.update({
character=char, apiinfo=apiinfo)) 'corpstats': corpstats.get_view_model(request.user),
'members': members,
})
logger.info("Found %s members for user %s matching search string %s" % ( return render(request, 'corputils/corpstats.html', context=context)
len(searchresults), request.user, searchstring))
context = {'corp': corp, 'results': searchresults, 'search_form': CorputilsSearchForm(),
"year": datetime.datetime.now().year, "month": datetime.datetime.now().month}
return render(request, 'registered/corputilssearchview.html', @login_required
context=context) @user_passes_test(access_corpstats_test)
else: def corpstats_update(request, corp_id):
logger.debug("Form invalid - returning for user %s to retry." % request.user) corp = get_object_or_404(EveCorporationInfo, corporation_id=corp_id)
context = {'corp': corp, 'members': None, 'search_form': CorputilsSearchForm()} corpstats = get_object_or_404(CorpStats, corp=corp)
return render(request, 'registered/corputilssearchview.html', context=context) if corpstats.can_update(request.user):
try:
else: corpstats.update()
logger.debug("Returning empty search form for user %s" % request.user) except HTTPError as e:
return redirect("auth_corputils") messages.error(request, str(e))
else: else:
logger.warn('User %s not authorized to view corp stats for corp ID %s' % (request.user, corpid)) raise PermissionDenied(
return redirect("auth_dashboard") 'You do not have permission to update member data for the selected corporation statistics module.')
if corpstats.pk:
return redirect('corputils:view_corp', corp_id=corp.corporation_id)
else:
return redirect('corputils:view')
@login_required
@user_passes_test(access_corpstats_test)
def corpstats_search(request):
results = []
search_string = request.GET.get('search_string', None)
if search_string:
has_similar = CorpStats.objects.filter(_members__icontains=search_string).visible_to(request.user)
for corpstats in has_similar:
similar = [(member_id, corpstats.members[member_id]) for member_id in corpstats.members if
search_string.lower() in corpstats.members[member_id].lower()]
for s in similar:
results.append(
(corpstats, CorpStats.MemberObject(s[0], s[1], show_apis=corpstats.show_apis(request.user))))
page = request.GET.get('page', 1)
results = sorted(results, key=lambda x: x[1].character_name)
results_page = get_page(results, page)
context = {
'available': CorpStats.objects.visible_to(request.user),
'results': results_page,
'search_string': search_string,
}
return render(request, 'corputils/search.html', context=context)
return redirect('corputils:view')

1
docs/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
_build/

20
docs/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = AllianceAuth
SOURCEDIR = .
BUILDDIR = _build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

173
docs/conf.py Normal file
View File

@@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
#
# Alliance Auth documentation build configuration file, created by
# sphinx-quickstart on Tue Jan 3 12:56:59 2017.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
sys.path.insert(0, os.path.abspath('.'))
# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
# Support for recommonmark module
import recommonmark
from recommonmark.parser import CommonMarkParser
from recommonmark.transform import AutoStructify
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = []
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = ['.md', '.rst']
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Alliance Auth'
copyright = u'2017, Alliance Auth'
author = u'R4stl1n'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = u'1.14'
# The full version, including alpha/beta/rc tags.
# release = u'1.14.0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'AllianceAuthdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'AllianceAuth.tex', u'Alliance Auth Documentation',
u'R4stl1n', 'manual'),
]
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'allianceauth', u'Alliance Auth Documentation',
[author], 1)
]
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'AllianceAuth', u'Alliance Auth Documentation',
author, 'AllianceAuth', 'Alliance service auth to help large scale alliances manage services.',
'Miscellaneous'),
]
# Markdown support
source_parsers = {
'.md': CommonMarkParser,
}
def setup(app):
app.add_config_value('recommonmark_config', {
'auto_toc_tree_section': 'Contents',
}, True)
app.add_transform(AutoStructify)

View File

@@ -0,0 +1,38 @@
# Documentation
The documentation for Alliance Auth uses [Sphinx](http://www.sphinx-doc.org/) to build documentation. When a new commit
to specific branches is made (master, primarily), the repository is automatically pulled, docs built and deployed on
[readthedocs.org](https://readthedocs.org/).
Documentation was migrated from the Github wiki pages and into the repository to allow documentation changes to be
included with pull requests. This means that documentation can be guaranteed to be updated when a pull request is
accepted rather than hoping documentation is updated afterwards or relying on maintainers to do the work. It also
allows for documentation to be maintained at different versions more easily.
## Building Documentation
If you're developing new documentation, its likely you'll want or need to test build it before committing to your
branch. To achieve this you can use Sphinx to build the documentation locally as it appears on Read the Docs.
Activate your virtual environment (if you're using one) and install the documentation requirements found in
`docs/requirements.txt` using pip, e.g. `pip install -r docs/requirements.txt`.
You can then build the docs by changing to the `docs/` directory and running `make html` or `make dirhtml`, depending
on how the Read the Docs project is configured. Either should work fine for testing. You can now find the output of the
build in the `/docs/_build/` directory.
Occasionally you may need to fully rebuild the documents by running `make clean` first, usually when you add or
rearrange toctrees.
## Documentation Format
CommonMark Markdown is the current preferred format, via [recommonmark](https://github.com/rtfd/recommonmark).
reStructuredText is supported if required, or you can execute snippets of reST inside Markdown by using a code block:
```eval_rst
reStructuredText here
```
Markdown is used elsewhere on Github so it provides the most portability of documentation from Issues and Pull Requests
as well as providing an easier initial migration path from the Github wiki.

View File

@@ -0,0 +1,9 @@
# Development
```eval_rst
.. toctree::
documentation
integrating-services
menu-hooks
```

View File

@@ -0,0 +1,302 @@
# Integrating Services
One of the primary roles of Alliance Auth is integrating with external services in order to authenticate and manage users. This is achieved through the use of service modules.
## The Service Module
Each service module is its own self contained Django app. It will likely contain views, models, migrations and templates. Anything that is valid in a Django app is valid in a service module.
Normally service modules live in `services.modules` though they may also be installed as external packages and installed via `pip` if you wish. A module is installed by including it in the `INSTALLED_APPS` setting.
### Service Module Structure
Typically a service will contain 5 key components:
- [The Hook](#the-hook)
- [The Service Manager](#the-service-manager)
- [The Views](#the-views)
- [The Tasks](#the-tasks)
- [The Models](#the-models)
The architecture looks something like this:
urls -------▶ Views
▲ |
| |
| ▼
ServiceHook ----▶ Tasks ----▶ Manager
|
|
AllianceAuth
Where:
Module --▶ Dependency/Import
While this is the typical structure of the existing services modules, there is no enforcement of this structure and you are, effectively, free to create whatever architecture may be necessary. A service module need not even communicate with an external service, for example, if similar triggers such as validate_user, delete_user are required for a module it may be convenient to masquerade as a service. Ideally though, using the common structure improves the maintainability for other developers.
### The Hook
In order to integrate with Alliance Auth service modules must provide a `services_hook`. This hook will be a function that returns an instance of the `services.hooks.ServiceHook` class and decorated with the `@hooks.registerhook` decorator. For example:
@hooks.register('services_hook')
def register_service():
return ExampleService()
This would register the ExampleService class which would need to be a subclass of `services.hooks.ServiceHook`.
```eval_rst
.. important::
The hook **MUST** be registered in `yourservice.auth_hooks` along with any other hooks you are registering for Alliance Auth.
```
A subclassed `ServiceHook` might look like this:
class ExampleService(ServicesHook):
def __init__(self):
ServicesHook.__init__(self)
self.urlpatterns = urlpatterns
self.service_url = 'http://exampleservice.example.com'
"""
Overload base methods here to implement functionality
"""
### The ServiceHook class
The base `ServiceHook` class defines function signatures that Alliance Auth will call under certain conditions in order to trigger some action in the service.
You will need to subclass `services.hooks.ServiceHook` in order to provide implementation of the functions so that Alliance Auth can interact with the service correctly. All of the functions are optional, so its up to you to define what you need.
Instance Variables:
- [self.name](#self-name)
- [self.urlpatterns](#self-url-patterns)
- [self.service_ctrl_template](#self-service-ctrl-template)
Properties:
- [title](#title)
Functions:
- [delete_user](#delete-user)
- [validate_user](#validate-user)
- [sync_nickname](#sync-nickname)
- [update_groups](#update-groups)
- [update_all_groups](#update-all-groups)
- [service_enabled_members](#service-enabled-members)
- [service_enabled_blues](#service-enabled-blues)
- [service_active_for_user](#service-active-for-user)
- [show_service_ctrl](#show-service-ctrl)
- [render_service_ctrl](#render-service-ctrl)
#### self.name
Internal name of the module, should be unique amongst modules.
#### self.urlpatterns
You should define all of your service urls internally, usually in `urls.py`. Then you can import them and set `self.urlpatterns` to your defined urlpatterns.
from . import urls
...
class MyService(ServiceHook):
def __init__(self):
...
self.urlpatterns = urls.urlpatterns
All of your apps defined urlpatterns will then be included in the `URLconf` when the core application starts.
#### self.service_ctrl_template
This is provided as a courtesy and defines the default template to be used with [render_service_ctrl](#render-service-ctrl). You are free to redefine or not use this variable at all.
#### title
This is a property which provides a user friendly display of your service's name. It will usually do a reasonably good job unless your service name has punctuation or odd capitalisation. If this is the case you should override this method and return a string.
#### delete_user
`def delete_user(self, user, notify_user=False):`
Delete the users service account, optionally notify them that the service has been disabled. The `user` parameter should be a Django User object. If notify_user is set to `True` a message should be set to the user via the `notifications` module to alert them that their service account has been disabled.
The function should return a boolean, `True` if successfully disabled, `False` otherwise.
#### validate_user
`def validate_user(self, user):`
Validate the users service account, deleting it if they should no longer have access. The `user` parameter should be a Django User object.
An implementation will probably look like the following:
def validate_user(self, user):
logger.debug('Validating user %s %s account' % (user, self.name))
if ExampleTasks.has_account(user) and not self.service_active_for_user(user):
self.delete_user(user, notify_user=True)
No return value is expected.
This function will be called periodically on all users to validate that the given user should have their current service accounts.
#### sync_nickname
`def sync_nickname(self, user):`
Very optional. As of writing only one service defines this. The `user` parameter should be a Django User object. When called, the given users nickname for the service should be updated and synchronised with the service.
If this function is defined, an admin action will be registered on the Django Users view, allowing admins to manually trigger this action for one or many users. The hook will trigger this action user by user, so you won't have to manage a list of users.
#### update_groups
`def update_groups(self, user):`
Update the users group membership. The `user` parameter should be a Django User object.
When this is called the service should determine the groups the user is a member of and synchronise the group membership with the external service. If you service does not support groups then you are not required to define this.
If this function is defined, an admin action will be registered on the Django Users view, allowing admins to manually trigger this action for one or many users. The hook will trigger this action user by user, so you won't have to manage a list of users.
This action is usually called via a signal when a users group membership changes (joins or leaves a group).
#### update_all_groups
`def update_all_groups(self):`
The service should iterate through all of its recorded users and update their groups.
I'm really not sure when this is called, it may have been a hold over from before signals started to be used. Regardless, it can be useful to server admins who may call this from a Django shell to force a synchronisation of all user groups for a specific service.
#### service_enabled_members
`def service_enabled_members(self):`
Is this service enabled for users with the `Member` state? It should return `True` if the service is enabled for Members, and `False` otherwise. The default is `False`.
An implementation will usually look like:
def service_enabled_members(self):
return settings.ENABLE_AUTH_EXAMPLE or False
```eval_rst
.. note::
There has been discussion about moving services to permissions based access. You should review `Issue #663 <https://github.com/allianceauth/allianceauth/issues/663/>`_.
```
#### service_enabled_blues
`def service_enabled_blues(self):`
Is this service enabled for users with the `Blue` state? It should return `True` if the service is enabled for Blues, and `False` otherwise. The default is `False`.
An implementation will usually look like:
def service_enabled_blues(self):
return settings.ENABLE_BLUE_EXAMPLE or False
```eval_rst
.. note::
There has been discussion about moving services to permissions based access. You should review `Issue #663 <https://github.com/allianceauth/allianceauth/issues/663/>`_.
```
#### service_active_for_user
`def service_active_for_user(self, user):`
Is this service active for the given user? The `user` parameter should be a Django User object.
Usually you wont need to override this as it calls `service_enabled_members` or `service_enabled_blues` depending on the users state.
```eval_rst
.. note::
There has been discussion about moving services to permissions based access. You should review `Issue #663 <https://github.com/allianceauth/allianceauth/issues/663/>`_ as this function will likely need to be defined by each service to check its permission.
```
#### show_service_ctrl
`def show_service_ctrl(self, user, state):`
Should the service be shown for the given `user` with the given `state`? The `user` parameter should be a Django User object, and the `state` parameter should be a valid state from `authentication.states`.
Usually you wont need to override this function.
For more information see the [render_service_ctrl](#render-service-ctrl) section.
#### render_service_ctrl
`def render_services_ctrl(self, request):`
Render the services control row. This will be called for all active services when a user visits the `/services/` page and [show_service_ctrl](#show-service-ctrl) returns `True` for the given user.
It should return a string (usually from `render_to_string`) of a table row (`<tr>`) with 4 columns (`<td>`). Column #1 is the service name, column #2 is the users username for this service, column #3 is the services URL, and column #4 is the action buttons.
You may either define your own service template or use the default one provided. The default can be used like this example:
def render_services_ctrl(self, request):
"""
Example for rendering the service control panel row
You can override the default template and create a
custom one if you wish.
:param request:
:return:
"""
urls = self.Urls()
urls.auth_activate = 'auth_example_activate'
urls.auth_deactivate = 'auth_example_deactivate'
urls.auth_reset_password = 'auth_example_reset_password'
urls.auth_set_password = 'auth_example_set_password'
return render_to_string(self.service_ctrl_template, {
'service_name': self.title,
'urls': urls,
'service_url': self.service_url,
'username': 'example username'
}, request=request)
the `Urls` class defines the available URL names for the 4 actions available in the default template:
- Activate (create service account)
- Deactivate (delete service account)
- Reset Password (random password)
- Set Password (custom password)
If you don't define one or all of these variable the button for the undefined URLs will not be displayed.
Most services will survive with the default template. If, however, you require extra buttons for whatever reason, you are free to provide your own template as long as you stick within the 4 columns. Multiple rows should be OK, though may be confusing to users.
### Menu Item Hook
If you services needs cannot be satisfied by the Service Control row, you are free to specify extra hooks by subclassing or instantiating the `services.hooks.MenuItemHook` class.
For more information see the [Menu Hooks](menu-hooks.md) page.
### The Service Manager
The service manager is what interacts with the external service. Ideally it should be completely agnostic about its environment, meaning that it should avoid calls to Alliance Auth and Django in general (except in special circumstances where the service is managed locally, e.g. Mumble). Data should come in already arranged by the Tasks and data passed back for the tasks to manage or distribute.
The reason for maintaining this separation is that managers may be reused from other sources and there may not even be a need to write a custom manager. Likewise, by maintaining this neutral environment others may reuse the managers that we write. It can also significantly ease the unit testing of services.
### The Views
As mentioned at the start of this page, service modules are fully fledged Django apps. This means you're free to do whatever you wish with your views.
Typically most traditional username/password services define four views.
- Create Account
- Delete Account
- Reset Password
- Set Password
These views should interact with the service via the Tasks, though in some instances may bypass the Tasks and access the manager directly where necessary, for example OAuth functionality.
### The Tasks
The tasks component is the glue that holds all of the other components of the service module together. It provides the function implementation to handle things like adding and deleting users, updating groups, validating the existence of a users account. Whatever tasks `auth_hooks` and `views` have with interacting with the service will probably live here.
### The Models
Its very likely that you'll need to store data about a users remote service account locally. As service modules are fully fledged Django apps you are free to create as many models as necessary for persistent storage. You can create foreign keys to other models in Alliance Auth if necessary, though I _strongly_ recommend you limit this to the User and Groups models from `django.contrib.auth.models` and query any other data manually.
If you create models you should create the migrations that go along with these inside of your module/app.
## Examples
There is a bare bones example service included in `services.modules.example`, you may like to use this as the base for your new service.
You should have a look through some of the other service modules before you get started to get an idea of the general structure of one. A lot of them aren't perfect so don't feel like you have to rigidly follow the structure of the existing services if you think its sub-optimal or doesn't suit the external service you're integrating.
## Testing
You will need to add unit tests for all aspects of your service module before it is accepted. Be mindful that you don't actually want to make external calls to the service so you should mock the appropriate components to prevent this behaviour.

View File

@@ -0,0 +1,38 @@
# Menu Hooks
```eval_rst
.. note::
Currently most menu items are statically defined in the `base.html` template. Ideally this behaviour will change over time with each module of Alliance Auth providing all of its menu items via the hook. New modules should aim to use the hook over statically adding menu items to the base template.
```
The menu hooks allow you to dynamically specify menu items from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.MenuItemHook` class and then register the menu item with one of the hooks.
There are three levels of Menu Item Hooks
- `menu_main_hook`
- `menu_aux_hook`
- `menu_util_hook`
These represent the 3 levels of menu displayed on the site.
To register a MenuItemHook class you would do the following:
@hooks.register('menu_util_hook')
def register_menu():
return MenuItemHook('Example Item', 'glyphicon glyphicon-heart', 'example_url_name', 150)
The `MenuItemHook` class specifies some parameters/instance variables required for menu item display.
`MenuItemHook(text, classes, url_name, order=None)`
#### text
The text value of the link
#### classes
The classes that should be applied to the bootstrap menu item icon
#### url_name
The name of the Django URL to use
#### order
An integer which specifies the order of the menu item, lowest to highest
If you cannot get the menu item to look the way you wish, you are free to subclass and override the default render function and the template used.

133
docs/features/corpstats.md Normal file
View File

@@ -0,0 +1,133 @@
# Corp Stats
This module is used to check the registration status of corp members and to determine character relationships, being mains or alts.
## Creating a Corp Stats
Upon initial install, nothing will be visible. For every corp, a model will have to be created before data can be viewed.
![nothing is visible](/_static/images/features/corpstats/blank_header.png)
If you are a superuser, the add button will be immediate visible to you. If not, your user account requires the `add_corpstats` permission.
Corp Stats requires an EVE SSO token to access data from the EVE Swagger Interface. Upon pressing the Add button, you will be prompted to authenticated. Please select the character who is in the corp you want data for.
![authorize from the EVE site](/_static/images/features/corpstats/eve_sso_authorization.png)
You will return to auth where you are asked to select a token with the green arrow button. If you want to use a different character, press the `LOG IN with EVE Online` button.
![select an SSO token to create with](/_static/images/features/corpstats/select_sso_token.png)
If this works (and you have permission to view the Corp Stats you just created) you'll be returned to a view of the Corp Stats.
If it fails an error message will be displayed.
## Corp Stats View
### Navigation Bar
![navigation bar](/_static/images/features/corpstats/navbar.png)
This bar contains a dropdown menu of all available corps. If the user has the `add_corpstats` permission, a button to add a Corp Stats will be shown.
On the right of this bar is a search field. Press enter to search. It checks all characters in all Corp Stats you have view permission to and returns search results. Generic searches (such as 'a') will be slow.
### API Index
![API Index](/_static/images/features/corpstats/api_index.png)
This is a visual indication of the number of registered characters.
### Last Update
![last update and update button](/_static/images/features/corpstats/last_update.png)
Corp Stats do not automatically update. They update once upon creation for initial data, and whenever someone presses the update button.
Only superusers and the creator of the Corp Stat can update it.
### Member List
![member list](/_static/images/features/corpstats/member_list.png)
The list contains all characters in the corp. Red backgrounds means they are not registered in auth. If registered, and the user has the required permission to view APIs, a link to JackKnife will be present.
A link to zKillboard is present for all characters.
If registered, the character will also have a main character, main corporation, and main alliance field.
This view is paginated: use the navigation arrows to view more pages (sorted alphabetically by character name), or search for a specific character.
![pagination buttons](/_static/images/features/corpstats/pagination.png)
## Search View
![search results](/_static/images/features/corpstats/search_view.png)
This view is essentially the same as the Corp Stats page, but not specific to a single corp.
The search query is visible in the search box.
Characters from all Corp Stats to which the user has view access will be displayed. APIs respect permissions.
This view is paginated: use the navigation arrows to view more pages (sorted alphabetically by character name).
## Permissions
To use this feature, users will require some of the following:
```eval_rst
+---------------------------------------+------------------+----------------------------------------------------+
| Permission | Admin Site | Auth Site |
+=======================================+==================+====================================================+
| corpstats.corp_apis | None | Can view API keys of members of their corporation. |
+---------------------------------------+------------------+----------------------------------------------------+
| corpstats.alliance_apis | None | Can view API keys of members of their alliance. |
+---------------------------------------+------------------+----------------------------------------------------+
| corpstats.blue_apis | None | Can view API keys of members of blue corporations. |
+---------------------------------------+------------------+----------------------------------------------------+
| corpstats.view_corp_corpstats | None | Can view corp stats of their corporation. |
+---------------------------------------+------------------+----------------------------------------------------+
| corpstats.view_alliance_corpstats | None | Can view corp stats of members of their alliance. |
+---------------------------------------+------------------+----------------------------------------------------+
| corpstats.view_blue_corpstats | None | Can view corp stats of blue corporations. |
+---------------------------------------+------------------+----------------------------------------------------+
| corpstats.add_corpstats | Can create model | Can add new corpstats using an SSO token. |
+---------------------------------------+------------------+----------------------------------------------------+
| corpstats.change_corpstats | Can edit model | None. |
+---------------------------------------+------------------+----------------------------------------------------+
| corpstats.remove_corpstats | Can delete model | None. |
+---------------------------------------+------------------+----------------------------------------------------+
```
Typical use-cases would see the bundling of `corp_apis` and `view_corp_corpstats`, same for alliances and blues.
Alliance permissions supersede corp permissions. Note that these evaluate against the user's main character.
## Troubleshooting
### Failure to create Corp Stats
>Unrecognized corporation. Please ensure it is a member of the alliance or a blue.
Corp Stats can only be created for corporations who have a model in the database. These only exist for tenant corps,
corps of tenant alliances, blue corps, and members of blue alliances.
>Selected corp already has a statistics module.
Only one Corp Stats may exist at a time for a given corporation.
>Failed to gather corporation statistics with selected token.
During initial population, the EVE Swagger Interface did not return any member data. This aborts the creation process. Please wait for the API to start working before attempting to create again.
### Failure to update Corp Stats
Any of the following errors will result in a notification to the owning user, and deletion of the Corp Stats model.
>Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.
This occurs when the SSO token is invalid, which can occur when deleted by the user, the character is transferred between accounts, or the API is having a bad day.
>CorpStats for corp_name cannot update with your ESI token as you have left corp.
The SSO token's character is no longer in the corp which the Corp Stats is for, and therefore membership data cannot be retrieved.
>HTTPForbidden
The SSO token lacks the required scopes to update membership data.

101
docs/features/groups.md Normal file
View File

@@ -0,0 +1,101 @@
# Groups
Group Management is one of the core tasks of Alliance Auth. Many of Alliance Auth's services allow for synchronising of group membership, allowing you to grant permissions or roles in services to access certain aspects of them.
## Automatic Groups
When a member registers in Alliance Auth and selects a main character, Auth will assign them some groups automatically based on some factors.
```eval_rst
.. important::
The ``Corp_`` and ``Alliance_`` group name prefixes are reserved for Alliance Auth internal group management. If you prefix a group with these you will find Alliance Auth automatically removes users from the group.
```
```eval_rst
+------------------------------+-----------------------------------------------------------------------------------+
| Group | Condition |
+------------------------------+-----------------------------------------------------------------------------------+
| ``Corp_<corp_name>`` | Users Main Character belongs to the Corporation |
+------------------------------+-----------------------------------------------------------------------------------+
| ``Alliance_<alliance_name>`` | Users Main Character belongs to the Alliance |
+------------------------------+-----------------------------------------------------------------------------------+
| ``Member`` | User is a member of one of the tenant Corps or Alliances |
+------------------------------+-----------------------------------------------------------------------------------+
| ``Blue`` | User is a member of a blue Corp or Alliance, be it via standings or static config |
+------------------------------+-----------------------------------------------------------------------------------+
```
When the user no longer has the condition required to be a member of that group they are automatically removed by Auth.
## User Organised Groups
Along with the automated groups, administrators can create custom groups for users to join. Examples might be groups like `Leadership`, `CEO` or `Scouts`.
When you create a Django `Group`, Auth automatically creates a corresponding `AuthGroup` model. The admin page looks like this:
![AuthGroup Admin page](/_static/images/features/group-admin.png)
Here you have several options:
#### Internal
Users cannot see, join or request to join this group. This is primarily used for Auth's internally managed groups, though can be useful if you want to prevent users from managing their membership of this group themselves. This option will override the Hidden, Open and Public options when enabled.
By default, every new group created will be an internal group.
#### Hidden
Group is hidden from the user interface, but users can still join if you give them the appropriate join link. The URL will be along the lines of `https://example.com/en/group/request_add/{group_id}`. You can get the Group ID from the admin page URL.
This option still respects the Open option.
### Open
When a group is toggled open, users who request to join the group will be immediately added to the group.
If the group is not open, their request will have to be approved manually by someone with the group management role, or a group leader of that group.
### Public
Group is accessible to any registered user, even when they do not have permission to join regular groups.
The key difference is that the group is completely unmanaged by Auth. **Once a member joins they will not be removed unless they leave manually, you remove them manually, or their account is deliberately set inactive or deleted.**
Most people won't have a use for public groups, though it can be useful if you wish to allow public access to some services. You can grant service permissions on a public group to allow this behaviour.
## Permission
In order to join a group other than a public group, the permission `groupmanagement.request_groups` (`Can request non-public groups` in the admin panel) must be active on their account, either via a group or directly applied to their User account.
When a user loses this permission, they will be removed from all groups _except_ Public groups.
```eval_rst
.. note::
By default, the ``groupmanagement.request_groups`` permission is applied to the ``Member`` group. In most instances this, and perhaps adding it to the ``Blue`` group, should be all that is ever needed. It is unsupported and NOT advisable to apply this permission to a public group. See #697 for more information.
```
# Group Management
In order to access group management, users need to be either a superuser, granted the `auth | user | group_management ( Access to add members to groups within the alliance )` permission or a group leader (discussed later).
## Group Requests
When a user joins or leaves a group which is not marked as "Open", their group request will have to be approved manually by a user with the `group_management` permission or by a group leader of the group they are requesting.
## Group Membership
The group membership tab gives an overview of all of the non-internal groups.
![Group overview](/_static/images/features/group-membership.png)
### Group Member Management
Clicking on the blue eye will take you to the group member management screen. Here you can see a list of people who are in the group, and remove members where necessary.
![Group overview](/_static/images/features/group-member-management.png)
## Group Leaders
Group leaders have the same abilities as users with the `group_management` permission, _however_, they will only be able to:
- Approve requests for groups they are a leader of.
- View the Group Membership and Group Members of groups they are leaders of.
This allows you to more finely control who has access to manage which groups. Currently it is not possible to add a Group as group leaders.

View File

@@ -0,0 +1,84 @@
# HR Applications
## Management
### Creating Forms
The most common task is creating ApplicationForm models for corps. Only when such models exist will a corp be listed as a choice for applicants. This occurs in the django admin site, so only staff have access.
The first step is to create questions. This is achieved by creating ApplicationQuestion models, one for each question. Titles are not unique.
Next step is to create the actual ApplicationForm model. It requires an existing EveCorporationInfo model to which it will belong. It also requires the selection of questions. ApplicationForm models are unique per corp: only one may exist for any given corp concurrently.
You can adjust these questions at any time. This is the preferred method of modifying the form: deleting and recreating will cascade the deletion to all received applications from this form which is usually not intended.
Once completed the corp will be available to receive applications.
### Reviewing Applications
Superusers can see all applications, while normal members with the required permission can view only those to their corp.
Selecting an application from the management screen will provide all the answers to the questions in the form at the time the user applied.
When a reviewer assigns themselves an application, they mark it as in progress. This notifies the applicant and permanently attached the reviewer to the application.
Only the assigned reviewer can approve/reject/delete the application if they possess the appropriate permission.
Any reviewer who can see the application can view the applicant's APIs if they possess the appropriate permission.
## Permissions
The following permissions have an effect on the website above and beyond their usual admin site functions.
```eval_rst
+---------------------------------------+------------------+----------------------------------------------------+
| Permission | Admin Site | Auth Site |
+=======================================+==================+====================================================+
| auth.human_resources | None | Can view applications and mark in progress |
+---------------------------------------+------------------+----------------------------------------------------+
| hrapplications.approve_application | None | Can approve applications |
+---------------------------------------+------------------+----------------------------------------------------+
| hrapplications.delete_application | Can delete model | Can delete applications |
+---------------------------------------+------------------+----------------------------------------------------+
| hrapplications.reject_applications | None | Can reject applications |
+---------------------------------------+------------------+----------------------------------------------------+
| hrapplications.view_apis | None | Can view applicant API keys, and audit in Jacknife |
+---------------------------------------+------------------+----------------------------------------------------+
| hrapplications.add_applicationcomment | Can create model | Can comment on applications |
+---------------------------------------+------------------+----------------------------------------------------+
```
Best practice is to bundle the `auth.human_resources` permission alongside the `hrapplications.approve_application` and `hrapplications.reject_application` permissions, as in isolation these don't make much sense.
## Models
### ApplicationQuestion
This is the model representation of a question. It contains a title, and a field for optional "helper" text. It is referenced by ApplicationForm models but acts independently. Modifying the question after it has been created will not void responses, so it's not advisable to edit the title or the answers may not make sense to reviewers.
### ApplicationForm
This is the template for an application. It points at a corp, with only one form allowed per corp. It also points at ApplicationQuestion models. When a user creates an application, they will be prompted with each question the form includes at the given time. Modifying questions in a form after it has been created will not be reflected in existing applications, so it's perfectly fine to adjust them as you see fit. Changing corps however is not advisable, as existing applications will point at the wrong corp after they've been submitted, confusing reviewers.
### Application
This is the model representation of a completed application. It references an ApplicationForm from which it was spawned which is where the corp specificity comes from. It points at a user, contains info regarding its reviewer, and has a status. Shortcut properties also provide the applicant's main character, the applicant's APIs, and a string representation of the reviewer (for cases when the reviewer doesn't have a main character or the model gets deleted).
### ApplicationResponse
This is an answer to a question. It points at the Application to which it belongs, to the ApplicationQuestion which it is answering, and contains the answer text. Modifying any of these fields in dangerous.
### ApplicationComment
This is a reviewer's comment on an application. Points at the application, points to the user, and contains the comment text. Modifying any of these fields is dangerous.
## Troubleshooting
### No corps accepting applications
Ensure there are ApplicationForm models in the admin site. Ensure the user does not already have an application to these corps. If the users wishes to re-apply they must first delete their completed application
### Reviewer unable to complete application
Reviewers require a permission for each of the three possible outcomes of an application, Approve Reject or Delete. Any user with the human resources permission can mark an application as in-progress, but if they lack these permissions then the application will get stuck. Either grant the user the required permissions or change the assigned reviewer in the admin site. Best practice is to bundle the `auth.human_resources` permission alongside the `hrapplications.approve_application` and `hrapplications.reject_application` permissions, as in isolation these don't serve much purpose.

12
docs/features/index.md Normal file
View File

@@ -0,0 +1,12 @@
# Features
```eval_rst
.. toctree::
:maxdepth: 1
:caption: Features Contents
hrapplications
corpstats
groups
permissions_tool
```

View File

@@ -0,0 +1,39 @@
# Permissions Auditing
```eval_rst
.. note::
New in 1.15
```
Access to most of Alliance Auth's features are controlled by Django's permissions system. In order to help you secure your services, Alliance Auth provides a permissions auditing tool.
### Access
In order to grant users access to the permissions auditing tool they will need to be granted the `permissions_tool.audit_permissions` permission or be a superuser.
When a user has access to the tool they will see the "Permissions Audit" menu item under the "Util" sub menu.
### Permissions Overview
The first page gives you a general overview of permissions and how many users have access to each permission.
![permissions overview](https://i.imgur.com/XALVFtc.png)
**App**, **Model** and **Code Name** contain the internal details of the permission while **Name** contains the name/description you'll see in the admin panel.
**Users** is the number of users explicitly granted this permission on their account.
**Groups** is the number of groups with this permission assigned.
**Groups Users** is the total number of users in all of the groups with this permission assigned.
Clicking on the **Code Name** link will take you to the [Permissions Audit Page](#permissions-audit-page)
### Permissions Audit Page
The permissions audit page will give you an overview of all the users who have access to this permission either directly or granted via group membership.
![permissions audit](https://i.imgur.com/XjnfC9Z.png)
Please note that users may appear multiple times if this permission is granted via multiple sources.

30
docs/index.md Normal file
View File

@@ -0,0 +1,30 @@
# Alliance Auth
Alliance service auth to help large scale alliances manage services. Built for "The 99 Percent" open for anyone to use
# Installing
[Ubuntu Setup Guide](installation/auth/ubuntu.md)
For other distros, adapt the procedure and find distro-specific alternatives for the [dependencies](installation/auth/dependencies.md)
# Using
See the [Quick Start Guide](installation/auth/quickstart.md) and learn about individual [features.](features/index.md)
# Troubleshooting
Read the [list of common problems.](maintenance/troubleshooting.md)
```eval_rst
.. toctree::
:maxdepth: 3
:caption: Contents
features/index
installation/index
maintenance/index
development/index
```

View File

@@ -0,0 +1,108 @@
# Apache Setup
### Overview
AllianceAuth gets served using a Web Server Gateway Interface (WSGI) script. This script passes web requests to AllianceAuth which generates the content to be displayed and returns it. This means very little has to be configured in Apache to host AllianceAuth.
In the interest of ~~laziness~~ time-efficiency, scroll down for example configs. Use these, changing the ServerName to your domain name.
If you're using a small VPS to host services with very limited memory resources, consider using NGINX with [Gunicorn](gunicorn.md). Even if you would like to use Apache, Gunicorn may give you lower memory usage over mod_wsgi.
### Required Parameters for AllianceAuth Core
The AllianceAuth core requires the following parameters to be set:
WSGIDaemonProcess
WSGIProcessGroup
WSGIScriptAlias
The following aliases are required:
Alias /static/ to point at the static folder
Alias /templates/ to point at the templates folder
## Description of Parameters
- `WSGIDaemonProcess` is the name of the process/application. It also needs to be passed the python-path parameter directing python to search the AllianceAuth directory for modules to load.
- `WSGIProcessGroup` is the group to run the process under. Typically the same as the name of the process/application.
- `WSGIScriptAlias` points to the WSGI script.
## Additional Parameters for Full Setup
To pass additional services the following aliases and directories are required:
- `Alias /forums` to point at the forums folder
- `Alias /killboard` to point at the killboard
Each of these require directory permissions allowing all connections.
For Apache 2.4 or greater:
<Directory "/path/to/alias/folder">
Require all granted
</Directory>
For Apache 2.3 or older:
<Directory "/path/to/alias/folder">
Order Deny,Allow
Allow from all
</Directory>
## SSL
You can supply your own SSL certificates if you so desire. The alternative is running behind cloudflare for free SSL.
## Sample Config Files
### Minimally functional config
```
<VirtualHost *:80>
ServerName example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www
WSGIDaemonProcess allianceauth python-path=/home/allianceserver/allianceauth
WSGIProcessGroup allianceauth
WSGIScriptAlias / /home/allianceserver/allianceauth/alliance_auth/wsgi.py
Alias /static/ /home/allianceserver/allianceauth/static/
<Directory /home/allianceserver/allianceauth/>
Require all granted
</Directory>
<Directory /var/www/>
Require all granted
</Directory>
</VirtualHost>
```
### Own SSL Cert
- Apache 2.4 or newer:
- [000-default.conf](http://pastebin.com/3LLzyNmV)
- [default-ssl.conf](http://pastebin.com/HUPPEp0R)
- Apache 2.3 or older:
- [000-default](http://pastebin.com/HfyKpQNu)
- [default-ssl](http://pastebin.com/2WCS5jnb)
### No SSL Cloudflare, or LetsEncrypt
- Apache 2.4 or newer:
- [000-default.conf](http://pastebin.com/j1Ps3ZK6)
- Apache 2.3 or older:
- [000-default](http://pastebin.com/BHQzf2pj)
To have LetsEncrypt automatically install SSL certs, comment out the three lines starting with `WSGI`, install certificates, then uncomment them in `000-default-ls-ssl.conf`
## Enabling and Disabling Sites
To instruct apache to serve traffic from a virtual host, enable it:
sudo a2ensite NAME
where NAME is the name of the configuration file (eg 000-default.conf)
To disable traffic from a site, disable the virtual host:
sudo a2dissite NAME
where NAME is the name of the configuration file (eg 000-default.conf)

View File

@@ -0,0 +1,84 @@
# CentOS Installation
It's recommended to update all packages before proceeding.
`sudo yum update`
`sudo yum upgrade`
`sudo reboot`
Now install all [dependencies](dependencies.md).
sudo yum install xxxxxxx
replacing the x's with the list of packages.
Make sure redis is running before continuing:
systemctl enable redis.service
systemctl start redis.service
For security and permissions, it's highly recommended you create a user to install under who is not the root account.
sudo adduser allianceserver
sudo passwd allianceserver
This user needs sudo powers. Add them by editing the sudoers file:
sudo nano /etc/sudoers
Find the line which says `root ALL=(ALL) ALL` - beneath it add another line `allianceserver ALL=(ALL) ALL` - now reboot.
**From this point on you need to be logged in as the allianceserver user**
Start your mariadb server `sudo systemctl start mariadb`
Secure your MYSQL / Maria-db server by typing `mysql_secure_installation `
AllianceAuth needs a MySQL user account. Create one as follows, replacing `PASSWORD` with an actual secure password:
mysql -u root -p
CREATE USER 'allianceserver'@'localhost' IDENTIFIED BY 'PASSWORD';
GRANT ALL PRIVILEGES ON * . * TO 'allianceserver'@'localhost';
Now we need to make the requisite databases.
create database alliance_auth;
create database alliance_forum;
create database alliance_jabber;
create database alliance_mumble;
create database alliance_killboard;
Ensure you are in the allianceserver home directory by issuing `cd`
Now we clone the source code:
git clone https://github.com/allianceauth/allianceauth.git
Enter the folder by issuing `cd allianceauth`
Ensure you're on the latest version with the following:
git checkout v1.15.7
Python package dependencies can be installed from the requirements file:
sudo pip install -r requirements.txt
The settings file needs configuring. See this lengthy guide for specifics.
Django needs to install models to the database before it can start.
python manage.py migrate
AllianceAuth needs to generate corp and alliance models before it can assign users to them.
python manage.py shell < run_alliance_corp_update.py
Now we need to round up all the static files required to render templates. Answer yes when prompted.
python manage.py collectstatic
Test the server by starting it manually.
python manage.py runserver 0.0.0.0:8000
If you see an error, stop, read it, and resolve it. If the server comes up and you can access it at `yourip:8000`, you're golden. It's ok to stop the server if you're going to be installing apache.

View File

@@ -0,0 +1,35 @@
# Cloudflare
CloudFlare offers free SSL and DDOS mitigation services. Why not take advantage of it?
## Setup
Youll need to register an account on [CloudFlares site.](https://www.cloudflare.com/)
Along the top bar, select `Add Site`
Enter your domain name. It will scan records and let you know you can add the site. Continue setup.
On the next page you should see an A record for example.com pointing at your server IP. If not, manually add one:
A example.com my.server.ip.address Automatic TTL
Add the record and ensure the cloud under Status is orange. If not, click it. This ensures traffic gets screened by CloudFlare.
If you want forums or kb on a subdomain, and want these to be protected by CloudFlare, add an additional record for for each subdomain in the following format, ensuring the cloud is orange:
CNAME subdomain example.com Automatic TTL
CloudFlare blocks ports outside 80 and 443 on hosts it protects. This means, if the cloud is orange, only web traffic will get through. We need to reconfigure AllianceAuth to provide services under a subdomain. Configure these subdomains as above, but ensure the cloud is not orange (arrow should go around a grey cloud).
## Redirect to HTTPS
Now we need to configure the https redirect to force all traffic to https. Along the top bar of CloudFlare, select `Page Rules`. Add a new rule, Pattern is example.com, toggle the `Always use https` to ON, and save. Itll take a few minutes to propagate.
![infographic](http://i.stack.imgur.com/VUBvo.jpg)
## Update Auth URLs
Edit settings.py and replace everything that has a HTTP with HTTPS (except anything with a port on the end, like `OPENFIRE_ADDRESS`)
And there we have it. Youre DDOS-protected with free SSL.

View File

@@ -0,0 +1,83 @@
# Dependencies
## Ubuntu
Tested on Ubuntu 12, 14, 15, and 16. Package names and repositories may vary.
### Core
Required for base auth site
#### Python
python python-dev python-mysqldb python-setuptools python-mysql.connector python-pip
#### MySQL
mysql-server mysql-client libmysqlclient-dev
#### Utilities
screen unzip git redis-server curl libssl-dev libbz2-dev libffi-dev
### Apache
Required for displaying web content
apache2 libapache2-mod-php5 libapache2-mod-wsgi
### PHP
```eval_rst
.. note::
If you are not planing to install either phpBB, smf, evernus alliance market, etc do not install these modules.
```
```eval_rst
.. important::
If you are not planing to use SeAT; php7.0 is the minimum required.
```
Required for phpBB, smf, evernus alliance market, etc
php5 php5-gd php5-mysqlnd php5-curl php5-gd php5-intl php-pear php5-imagick php5-imap php5-mcrypt php5-memcache php5-ming php5-ps php5-pspell php5-recode php5-snmp php5-sqlite php5-tidy php5-xmlrpc php5-xsl
### Java
Required for hosting jabber server
oracle-java8-installer
## CentOS 7
### Add The EPEL Repository
yum --enablerepo=extras install epel-release
yum update
### Core
Required for base auth site
#### Python
python python-devel MySQL-python python-setuptools mysql-connector-python python-pip bzip2-devel
#### MySQL
mariadb-server mariadb-devel mariadb
#### Utilities
screen gcc gcc-c++ unzip git redis curl nano
### Apache
Required for displaying web content
httpd mod_wsgi
### PHP
Required for phpBB, smf, evernus alliance market, etc
php php-gd php-mysqlnd php-intl php-pear ImageMagick php-imap php-mcrypt php-memcache php-pspell php-recode php-snmp php-pdo php-tidy php-xmlrpc
### Java
Required for hosting jabber server
java libstdc++.i686

View File

@@ -0,0 +1,121 @@
# Gunicorn
[Gunicorn](http://gunicorn.org) is a Python WSGI HTTP Server for UNIX. The Gunicorn server is light on server resources, and fairly speedy.
If you find Apache's `mod_wsgi` to be a headache or want to use NGINX (or some other webserver), then Gunicorn could be for you. There are a number of other WSGI server options out there and this documentation should be enough for you to piece together how to get them working with your environment.
Check out the full [Gunicorn docs](http://docs.gunicorn.org/en/latest/index.html).
## Setting up Gunicorn
```eval_rst
.. note::
If you're using a virtual environment (and I would encourage you to do so when hosting Alliance Auth), activate it now. `source /path/to/venv/bin/activate`.
```
Install Gunicorn using pip, `pip install gunicorn`.
In your `allianceauth` base directory, try running `gunicorn --bind 0.0.0.0:8000 alliance_auth.wsgi`. You should be able to browse to http://yourserver:8000 and see your Alliance Auth installation running. Images and styling will be missing, but dont worry, your web server will provide them.
Once you validate its running, you can kill the process with Ctrl+C and continue.
## Running Gunicorn with Supervisor
You should use [Supervisor](supervisor.md) to keep all of Alliance Auth components running (instead of using screen). You don't _have to_ but we will be using it to start and run Gunicorn so you might as well.
### Sample Supervisor config
You'll want to edit `/etc/supervisor/conf.d/aauth_gunicorn.conf` (or whatever you want to call the config file)
```
[program:aauth-gunicorn]
user = www-data
directory=/home/allianceserver/allianceauth/
command=gunicorn alliance_auth.wsgi --workers=3 --timeout 120
autostart=true
autorestart=true
stopsignal=INT
```
- `[program:aauth-gunicorn]` - Change aauth-gunicorn to whatever you wish to call your process in Supervisor.
- `user = www-data` - Change to whatever user you wish Gunicorn to run as. You could even set this as allianceserver if you wished. I'll leave the question security of that up to you.
- `directory=/home/allianceserver/allianceauth/` - Needs to be the path to your Alliance Auth install.
- `command=gunicorn alliance_auth.wsgi --workers=3 --timeout 120` - Running Gunicorn and the options to launch with. This is where you have some decisions to make, we'll continue below.
#### Gunicorn Arguments
See the [Commonly Used Arguments](http://docs.gunicorn.org/en/latest/run.html#commonly-used-arguments) or [Full list of settings](http://docs.gunicorn.org/en/stable/settings.html) for more information.
##### Where to bind Gunicorn to?
What address are you going to use to reference it? By default, without a bind parameter, Gunicorn will bind to `127.0.0.1:8000`. This might be fine for your application. If it clashes with another application running on that port you will need to change it. I would suggest using UNIX sockets too, if you can.
For UNIX sockets add `--bind=unix:/run/allianceauth.sock` (or to a path you wish to use). Remember that your web server will need to be able to access this socket file.
For a TCP address add `--bind=127.0.0.1:8001` (or to the address/port you wish to use, but I would strongly advise against binding it to an external address).
Whatever you decide to use, remember it because we'll need it when configuring your webserver.
##### Number of workers
By default Gunicorn will spawn only one worker. The number you set this to will depend on your own server environment, how many visitors you have etc. Gunicorn suggests between 2-4 workers per core. Really you could probably get away with 2-4 in total for most installs.
Change it by adding `--workers=2` to the command.
##### Running with a virtual environment
If you're running with a virtual environment, you'll need to add the path to the `command=` config line.
e.g. `command=/path/to/venv/bin/gunicorn alliance_auth.wsgi`
### Starting via Supervisor
Once you have your configuration all sorted, you will need to reload your supervisor config `sudo service supervisor reload` and then you can start the Gunicorn server via `sudo supervisorctl start aauth-gunicorn` (or whatever you renamed it to). You should see something like the following `aauth-gunicorn: started`. If you get some other message, you'll need to consult the Supervisor log files, usually found in `/var/log/supervisor/`.
## Configuring your webserver
### NGINX
To your server config add:
```
location / {
proxy_pass http://127.0.0.1:8000;
proxy_read_timeout 90;
proxy_redirect http://127.0.0.1:8000/ http://$host/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
Set `proxy_pass` and `proxy_redirect` to the address you set under `--bind=`. Set the second part of `proxy_redirect` to the URL you're hosting services on. Tell NGINX to reload your config, job done. Enjoy your lower memory usage and better performance!
If PHP is stopping you moving to NGINX, check out php-fpm as a way to run your PHP applications.
### Apache
If you were using mod_wsgi before, make a backup of your old config first and then strip out all of the mod_wsgi config from your Apache VirtualHost first config.
Your config will need something along these lines:
```
ProxyPreserveHost On
<Location />
SSLRequireSSL
ProxyPass http://127.0.0.1:8000/
ProxyPassReverse http://127.0.0.1:8000/
RequestHeader set X-FORWARDED-PROTOCOL ssl
RequestHeader set X-FORWARDED-SSL on
</Location>
```
Set `ProxyPass` and `ProxyPassReverse` addresses to your `--bind=` address set earlier.
You will need to enable some Apache mods. `sudo a2enmod http_proxy` should take care of the dependencies.
Restart Apache and you should be done.
### Other web servers
Any web server capable of proxy passing should be able to sit in front of Gunicorn. Consult their documentation armed with your `--bind=` address and you should be able to find how to do it relatively easy.
## Restarting Gunicorn
In the past when you made changes you restarted the entire Apache server. This is no longer required. When you update or make configuration changes that ask you to restart Apache, instead you can just restart Gunicorn:
`sudo supervisorctl restart aauth-gunicorn`, or the service name you chose for it.

View File

@@ -0,0 +1,16 @@
# Auth
```eval_rst
.. toctree::
dependencies
ubuntu
centos
settings
nginx
apache
gunicorn
cloudflare
supervisor
quickstart
```

View File

@@ -0,0 +1,93 @@
# NGINX
## Overivew
Nginx (engine x) is a HTTP server known for its high performance, stability, simple configuration, and low resource consumption. Unlike traditional servers (i.e. Apache), Nginx doesn't rely on threads to serve requests, rather using an asynchronous event driven approach which permits predictable resource usage and performance under load.
If you're trying to cram Alliance Auth into a very small VPS of say, 1-2GB or less, then Nginx will be considerably friendlier to your resources compared to Apache.
You can read more about NGINX on the [NGINX wiki](https://www.nginx.com/resources/wiki/).
## Coming from Apache
If you're converting from Apache, here are some things to consider.
Nginx is lightweight for a reason. It doesn't try to do everything internally and instead concentrates on just being a good HTTP server. This means that, unlike Apache, it wont automatically run PHP scripts via mod_php and doesn't have an internal WSGI server like mod_wsgi. That doesn't mean that it can't, just that it relies on external processes to run these instead. This might be good or bad depending on your outlook. It's good because it allows you to segment your applications, restarting Alliance Auth wont impact your PHP applications. On the other hand it means more config and more management of services. For some people it will be worth it, for others losing the centralised nature of Apache may not be worth it.
```eval_rst
+-----------+----------------------------------------+
| Apache | Nginx Replacement |
+===========+========================================+
| mod_php | php5-fpm or php7-fpm (PHP FastCGI) |
+-----------+----------------------------------------+
| mod_wsgi | Gunicorn or other external WSGI server |
+-----------+----------------------------------------+
```
Your .htaccess files wont work. Nginx has a separate way of managing access to folders via the server config. Everything you can do with htaccess files you can do with Nginx config. [Read more on the Nginx wiki](https://www.nginx.com/resources/wiki/start/topics/examples/likeapache-htaccess/)
## Setting up Nginx
Install Nginx via your preferred package manager or other method. If you need help just search, there are plenty of guides on installing Nginx out there.
You will need to have [Gunicorn](gunicorn.md) or some other WSGI server setup for hosting Alliance Auth.
Create a config file in `/etc/nginx/sites-available` call it `alliance-auth.conf` or whatever your preferred name is and copy the basic config in. Make whatever changes you feel are necessary.
Create a symbolic link to enable the site `sudo ln -s /etc/nginx/sites-available/alliance-auth.conf /etc/nginx/sites-enabled/` and then reload Nginx for the config to take effect, `sudo service nginx reload` for Ubuntu.
### Basic config
```
server {
listen 80;
server_name example.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
alias /home/allianceserver/allianceauth/static/;
autoindex off;
}
# Gunicorn config goes below
location / {
include proxy_params;
proxy_pass http://127.0.0.1:8000;
}
}
```
#### Adding TLS/SSL
With [Let's Encrypt](https://letsencrypt.org/) offering free SSL certificates, there's no good reason to not run HTTPS anymore.
Your config will need a few additions once you've got your certificate.
```
listen 443 ssl http2; # Replace listen 80; with this
ssl_certificate /path/to/your/cert.crt;
ssl_certificate_key /path/to/your/cert.key;
ssl on;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS;
ssl_prefer_server_ciphers on;
```
If you want to redirect all your non-SSL visitors to your secure site, below your main configs `server` block, add the following:
```
server {
listen 80;
server_name example.com;
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://$host$request_uri;
}
```
If you have trouble with the `ssl_ciphers` listed here or some other part of the SSL config, try getting the values from [Mozilla's SSL Config Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/).

View File

@@ -0,0 +1,14 @@
# Quick Start
Once youve installed AllianceAuth, perform these steps to get yourself up and running.
First you need a superuser account. You can use this as a personal account. From the command line, `python manage.py createsuperuser` and follow the prompts.
The big goal of AllianceAuth is the automation of group membership, so well need some groups. In the admin interface, select `Groups`, then at the top-right select `Add Group`. Give it a name and select permissions. Special characters (including spaces) are removing before syncing to services, so try not to have group names which will be the same upon cleaning. A description of permissions can be found in the [readme file](https://github.com/allianceauth/allianceauth/blob/master/README.md). Repeat for all the groups you see fit, whenever you need a new one.
### Background Processes
To start the background processes to sync groups and check api keys, issue these commands:
screen -dm bash -c 'celery -A alliance_auth worker'
screen -dm bash -c 'celery -A alliance_auth beat'

View File

@@ -0,0 +1,376 @@
# Settings Overview
The `alliance_auth/settings.py` file is used to pass settings to the django app needed to run.
### Words of Warning
Certain fields are quite sensitive to leading `http://` and trailing `/` - if you see these present in the default text, be sure to include them in your values.
Every variable value is opened and closed with a single apostrophe `'` - please do not include these in your values or it will break things. If you absolutely must, replace them at the opening and closing of the value with double quotes `"`.
Certain variables are booleans, and come in a form that looks like this:
MEMBER_CORP_GROUPS = 'True' == os.environ.get('AA_MEMBER_CORP_GROUPS', 'True')
They're handled as strings because when settings are exported from shell commands (eg `export AA_MEMBER_CORP_GROUPS False`) they're interpreted as strings, so a string comparison is done.
When changing these booleans, edit the setting within the brackets (eg `('AA_MEMBER_CORP_GROUPS', 'True')` vs `('AA_MEMBER_CORP_GROUPS', 'False')`) and not the `True` earlier in the statement. Otherwise these will have unexpected behaviours.
# Fields to Modify
## Required
- [SECRET_KEY](#secret-key)
- Use [this tool](http://www.miniwebtool.com/django-secret-key-generator/) to generate a key on initial install
- [DEBUG](#debug)
- If issues are encountered, set this to `True` to view a more detailed error report, otherwise set `False`
- [ALLOWED_HOSTS](#allowed-hosts)
- This restricts web addresses auth will answer to. Separate with commas.
- Should include localhost `127.0.0.1` and `example.com`
- To allow from all, include `'*'`
- [DATABASES](#databases)
- Fill out the database name and user credentials to manage the auth database.
- [DOMAIN](#domain)
- Set to the domain name AllianceAuth will be accessible under
- [EMAIL_HOST_USER](#email-host-user)
- Username to send emails from. If gmail account, the full gmail address.
- [EMAIL_HOST_PASSWORD](#email-host-password)
- Password for the email user.
- [CORP_IDS](#corp-ids)
- List of corp IDs who are members. Exclude if their alliance is in `ALLIANCE_IDS`
- [ALLIANCE_IDS](#alliance-ids)
- List of alliance IDs who are members.
- [ESI_SSO_CLIENT_ID](#esi-sso-client_id)
- EVE application ID from the developers site. See the [SSO Configuration Instruction](#sso-settings)
- [ESI_SSO_CLIENT_SECRET](#esi-sso-client-secret)
- EVE application secret from the developers site.
- [ESI_SSO_CALLBACK_URL](#esi-sso-callback-url)
- OAuth callback URL. Should be `https://mydomain.com/sso/callback`
## Services
### IPBoard
If using IPBoard, the following need to be set in accordance with the [install instructions](../services/ipboard3.md)
- [IPBOARD_ENDPOINT](#ipboard-endpoint)
- [IPBOARD_APIKEY](#ipboard-apikey)
- [IPBOARD_APIMODULE](#ipboard-apimodule)
### XenForo
If using XenForo, the following need to be set in accordance with the [install instructions](../services/xenforo.md)
- [XENFORO_ENDPOINT](#xenforo-endpoint)
- [XENFORO_APIKEY](#xenforo-apikey)
### Openfire
If using Openfire, the following need to be set in accordance with the [install instructions](../services/openfire.md)
- [JABBER_URL](#jabber-url)
- [JABBER_PORT](#jabber-port)
- [JABBER_SERVER](#jabber-server)
- [OPENFIRE_ADDRESS](#openfire-address)
- [OPENFIRE_SECRET_KEY](#openfire-secret-key)
- [BROADCAST_USER](#broadcast-user)
- [BROADCAST_USER_PASSWORD](#broadcast-user-password)
- [BROADCAST_SERVICE_NAME](#broadcast-service-name)
- [BROADCAST_IGNORE_INVALID_CERT](#broadcast-ignore-invalid-cert)
### Mumble
If using Mumble, the following needs to be set to the address of the mumble server:
- [MUMBLE_URL](#mumble-url)
### PHPBB3
If using phpBB3, the database needs to be defined.
### Teamspeak3
If using Teamspeak3, the following need to be set in accordance with the [install instrictions](../services/teamspeak3.md)
- [TEAMSPEAK3_SERVER_IP](#teamspeak3-server-ip)
- [TEAMSPEAK3_SERVER_PORT](#teamspeak3-server-port)
- [TEAMSPEAK3_SERVERQUERY_USER](#teamspeak3-serverquery-user)
- [TEAMSPEAK3_SERVERQUERY_PASSWORD](#teamspeak3-serverquery-password)
- [TEAMSPEAK3_VIRTUAL_SERVER](#teamspeak3-virtual-server)
- [TEAMSPEAK3_PUBLIC_URL](#teamspeak3-public-url)
### Discord
If connecting to a Discord server, set the following in accordance with the [install instructions](../services/discord.md)
- [DISCORD_GUILD_ID](#discord-guild-id)
- [DISCORD_BOT_TOKEN](#discord-bot-token)
- [DISCORD_INVITE_CODE](#discord-invite-code)
- [DISCORD_APP_ID](#discord-app-id)
- [DISCORD_APP_SECRET](#discord-app-secret)
- [DISCORD_CALLBACK_URL](#discord-callback-url)
- [DISCORD_SYNC_NAMES](#discord-sync-names)
### Discourse
If connecting to Discourse, set the following in accordance with the [install instructions](../services/discourse.md)
- [DISCOURSE_URL](#discourse-url)
- [DISCOURSE_API_USERNAME](#discourse-api-username)
- [DISCOURSE_API_KEY](#discourse-api-key)
- [DISCOURSE_SSO_SECRET](#discourse-sso-secret)
### IPSuite4
If using IPSuite4 (aka IPBoard4) the following are required:
- [IPS4_URL](#ips4-url)
- the database needs to be defined
### SMF
If using SMF the following are required:
- [SMF_URL](#smf-url)
- the database needs to be defined
## Optional
### Standings
To allow access to blues, a corp API key is required to pull standings from. Corp does not need to be owning corp or in owning alliance. Required mask is 16 (Communications/ContactList)
- [CORP_API_ID](#corp-api-id)
- [CORP_API_VCODE](#corp-api-vcode)
### API Key Audit URL
To define what happens when an API is clicked, set according to [these instructions](#hr-configuration)
- [API_KEY_AUDIT_URL](#api-key-audit-url)
### Auto Groups
Groups can be automatically assigned based on a user's corp or alliance. Set the following to `True` to enable this feature.
- [MEMBER_CORP_GROUPS](#member-corp-groups)
- [MEMBER_ALLIANCE_GROUPS](#member-alliance-groups)
- [BLUE_CORP_GROUPS](#blue-corp-groups)
- [BLUE_ALLIANCE_GROUPS](#blue-alliance-groups)
### Fleet-Up
Fittings and operations can be imported from Fleet-Up. Define the following to do so.
- [FLEETUP_APP_KEY](#fleetup-app-key)
- [FLEETUP_USER_ID](#fleetup-user-id)
- [FLEETUP_API_ID](#fleetup-api-id)
- [FLEETUP_GROUP_ID](#fleetup-group-id)
### CAPTCHA
To help prevent bots from registering and brute forcing the login. Get the reCaptcha keys from [here](https://www.google.com/recaptcha/intro/index.html)
- [CAPTCHA_ENABLED](#captcha_enabled)
- [RECAPTCHA_PUBLIC_KEY](#recaptcha_public_key)
- [RECAPTCHA_PRIVATE_KEY](#recaptcha_private_key)
- [NOCAPTCHA](#nocaptcha)
# Description of Settings
## Django
### SECRET_KEY
A random string used in cryptographic functions, such as password hashing. Changing after installation will render all sessions and password reset tokens invalid.
### DEBUG
Replaces the generic `SERVER ERROR (500)` page when an error is encountered with a page containing a traceback and variables. May expose sensitive information so not recommended for production.
### ALLOWED_HOSTS
A list of addresses used to validate headers: AllianceAuth will block connection coming from any other address. This should be a list of URLs and IPs to allow. In most cases, just adding `'example.com'` is sufficient. This also accepts the `'*'` wildcard for testing purposes.
### DATABASES
List of databases available. Contains the Django database, and may include service ones if enabled. Service databases are defined in their individual sections and appended as needed automatically.
### LANGUAGE_CODE
Friendly name of the local language.
### TIME_ZONE
Friendly name of the local timezone.
### CAPTCHA_ENABLED
Enable Google reCaptcha
### RECAPTCHA_PUBLIC_KEY
Google reCaptcha public key
### RECAPTCHA_PRIVATE_KEY
Google reCaptcha private key
### NOCAPTCHA
Enable New No Captcha reCaptcha
### STATIC_URL
Absolute URL to serve static files from.
### STATIC_ROOT
Root folder to store static files in.
### SUPERUSER_STATE_BYPASS
Overrides superuser account states to always return True on membership tests. If issues are encountered, or you want to test access to certain portions of the site, set to False to respect true states of superusers.
## EMAIL SETTINGS
### DOMAIN
The URL to which emails will link.
### EMAIL_HOST
The host address of the email server.
### EMAIL_PORT
The host port of the email server.
### EMAIL_HOST_USER
The username to authenticate as on the email server. For GMail, this is the full address.
### EMAIL_HOST_PASSWORD
The password of the user used to authenticate on the email server.
### EMAIL_USE_TLS
Enable TLS connections to the email server. Default is True.
## Front Page Links
### KILLBOARD_URL
Link to a killboard.
### EXTERNAL_MEDIA_URL
Link to another media site, eg YouTube channel.
### FORUM_URL
Link to forums. Also used as the phpbb3 URL if enabled.
### SITE_NAME
Name to show in the top-left corner of auth.
## SSO Settings
An application will need to be created on the developers site. Please select `Authenticated API Access`, and choose all scopes starting with `esi`.
### ESI_SSO_CLIENT_ID
The application cliend ID generated from the [developers site.](https://developers.eveonline.com)
### ESI_SSO_CLIENT_SECRET
The application secret key generated from the [developers site.](https://developers.eveonline.com)
### ESI_SSO_CALLBACK_URL
The callback URL for authentication handshake. Should be `https://example.com/sso/callback`.
## Default Group Settings
### DEFAULT_AUTH_GROUP
Name of the group members of the owning corp or alliance are put in.
### DEFAULT_BLUE_GROUP
Name of the group blues of the owning corp or alliance are put in.
### MEMBER_CORP_GROUPS
If `True`, add members to groups with their corp name, prefixed with `Corp_`
### MEMBER_ALLIANCE_GROUPS
If `True`, add members to groups with their alliance name, prefixed with `Alliance_`
### BLUE_CORP_GROUPS
If `True`, add blues to groups with their corp name, prefixed with `Corp_`
### BLUE_ALLIANCE_GROUPS
If `True`, add blues to groups with their alliance name, prefixed with `Alliance_`
## Tenant Configuration
Characters of any corp or alliance with their ID here will be treated as a member.
### CORP_IDS
EVE corp IDs of member corps. Separate with a comma.
### ALLIANCE_IDS
EVE alliance IDs of member alliances. Separate with a comma.
## Standings Configuration
To allow blues to access auth, standings must be pulled from a corp-level API. This API needs access mask 16 (ContactList).
### CORP_API_ID
The ID of an API key for a corp from which to pull standings, if desired. Needed for blues to gain access.
### CORP_API_VCODE
The verification code of an API key for a corp from which to pull standings, if desired. Needed for blues to gain access.
### BLUE_STANDING
The minimum standing value to consider blue. Default is 5.0
### STANDING_LEVEL
Standings from the API come at two levels: `corp` and `alliance`. Select which level to consider here.
## API Configuration
### MEMBER_API_MASK
Required access mask for members' API keys to be considered valid.
### MEMBER_API_ACCOUNT
If `True`, require API keys from members to be account-wide, not character-restricted.
### BLUE_API_MASK
Required access mask for blues' API keys to be considered valid.
### BLUE_API_ACCOUNT
If `True`, require API keys from blues to be account-wide, not character-restricted.
### REJECT_OLD_APIS
Require each submitted API be newer than the latest submitted API. Protects against recycled or stolen API keys.
### REJECT_OLD_APIS_MARGIN
Allows newly submitted APIs to have their ID this value lower than the highest API ID on record and still be accepted. Default is 50, 0 is safest.
## EVE Provider Settings
Data about EVE objects (characters, corps, alliances) can come from two sources: the XML API or the EVE Swagger Interface.
These settings define the default source.
For most situations, the EVE Swagger Interface is best. But if it goes down or experiences issues, these can be reverted to the XML API.
Accepted values are `esi` and `xml`.
### EVEONLINE_CHARACTER_PROVIDER
The default data source to get character information. Default is `esi`
### EVEONLINE_CORP_PROVIDER
The default data source to get corporation information. Default is `esi`
### EVEONLINE_ALLIANCE_PROVIDER
The default data source to get alliance information. Default is `esi`
### EVEONLINE_ITEMTYPE_PROVIDER
The default data source to get item type information. Default is `esi`
## Alliance Market
### MARKET_URL
The web address to access the Evernus Alliance Market application.
### MARKET_DB
The Evernus Alliance Market database connection information.
## HR Configuration
### API_KEY_AUDIT_URL
This setting defines what happens when someone clicks on an API key (such as in corpstats or an application).
Default behaviour is to show the verification code in a popup, but this can be set to link out to a website.
The URL set here uses python string formatting notation. Variable names are enclosed in `{}` brackets. Three variable names are available: `api_id`, `vcode`, and `pk` (which is the primary key of the API in the database - only useful on the admin site).
Example URL structures are provided. Jacknife can be installed on your server following [its setup guide.](../services/jacknife.md)
## IPBoard3 Configuration
### IPBOARD_ENDPOINT
URL to the `index.php` file of a IPBoard install's API server.
### IPBOARD_APIKEY
API key for accessing an IPBoard install's API
### IPBOARD_APIMODULE
Module to access while using the API
## XenForo Configuration
### XENFORO_ENDPOINT
The address of the XenForo API. Should look like `https://example.com/forum/api.php`
### XENFORO_DEFAULT_GROUP
The group ID of the group to assign to member. Default is 0.
### XENFORO_APIKEY
The API key generated from XenForo to allow API access.
## Jabber Configuration
### JABBER_URL
Address to instruct members to connect their jabber clients to, in order to reach an Openfire install. Usually just `example.com`
### JABBER_PORT
Port to instruct members to connect their jabber clients to, in order to reach an Openfire install. Usually 5223.
### JABBER_SERVER
Server name of an Openfire install. Usually `example.com`
### OPENFIRE_ADDRESS
URL of the admin web interface for an Openfire install. Usually `http://example.com:9090`. If HTTPS is desired, change port to 9091: `https://example.com:9091`
### OPENFIRE_SECRET_KEY
Secret key used to authenticate with an Openfire admin interface.
### BROADCAST_USER
Openfire user account used to send broadcasts from. Default is `Broadcast`.
### BROADCAST_USER_PASSWORD
Password to use when authenticating as the `BROADCAST_USER`
### BROADCAST_SERVICE_NAME
Name of the broadcast service running on an Openfire install. Usually `broadcast`
## Mumble Configuration
### MUMBLE_URL
Address to instruct members to connect their Mumble clients to.
### MUMBLE_SERVER_ID
Depreciated. We're too scared to delete it.
## Teamspeak3 Configuration
### TEAMSPEAK3_SERVER_IP
IP of a Teamspeak3 server on which to manage users. Usually `127.0.0.1`
### TEAMSPEAK3_SERVER_PORT
Port on which to connect to a Teamspeak3 server at the `TEAMSPEAK3_SERVER_IP`. Usually `10011`
### TEAMSPEAK3_SERVERQUERY_USER
User account with which to authenticate on a Teamspeak3 server. Usually `serveradmin`.
### TEAMSPEAK3_SERVERQUERY_PASSWORD
Password to use when authenticating as the `TEAMSPEAK3_SERVERQUERY_USER`. Provided during first startup or when you define a custom serverquery user.
### TEAMSPEAK3_VIRTUAL_SERVER
ID of the server on which to manage users. Usually `1`.
### TEAMSPEAK3_PUBLIC_URL
Address to instruct members to connect their Teamspeak3 clients to. Usually `example.com`
## Discord Configuration
### DISCORD_GUILD_ID
The ID of a Discord server on which to manage users.
### DISCORD_BOT_TOKEN
The bot token obtained from defining a bot on the [Discord developers site.](https://discordapp.com/developers/applications/me)
### DISCORD_INVITE_CODE
A no-limit invite code required to add users to the server. Must be generated from the Discord server itself (instant invite).
### DISCORD_APP_ID
The application ID obtained from defining an application on the [Discord developers site.](https://discordapp.com/developers/applications/me)
### DISCORD_APP_SECRET
The application secret key obtained from defining an application on the [Discord developers site.](https://discordapp.com/developers/applications/me)
### DISCORD_CALLBACK_URL
The callback URL used for authenticaiton flow. Should be `https://example.com/discord_callback`. Must match exactly the one used when defining the application.
### DISCORD_SYNC_NAMES
Override usernames on the server to match the user's main character.
## Discourse Configuration
### DISCOURSE_URL
The web address of the Discourse server to direct users to.
### DISCOURSE_API_USERNAME
Username of the account which generated the API key on Discourse.
### DISCOURSE_API_KEY
API key defined on Discourse.
### DISCOURSE_SSO_SECRET
The SSO secret key defined on Discourse.
## IPS4 Configuration
### IPS4_URL
URL of the IPSuite4 install to direct users to.
### IPS4_API_KEY
Depreciated. We're too scared to delete it.
### IPS4_DB
The database connection to manage users on.
## SMF Configuration
### SMF_URL
URL of the SMF install to direct users to.
### SMF_DB
The database connection to manage users on.
## Fleet-Up Configuration
### FLEETUP_APP_KEY
Application key as [defined on Fleet-Up.](http://fleet-up.com/Api/MyApps)
### FLEETUP_USER_ID
API user ID as [defined on Fleet-Up.](http://fleet-up.com/Api/MyKeys)
### FLEETUP_API_ID
API ID as [defined on Fleet-Up.](http://fleet-up.com/Api/MyKeys)
### FLEETUP_GROUP_ID
The group ID from which to pull data. Can be [retrieved from Fleet-Up](http://fleet-up.com/Api/Endpoints#groups_mygroupmemberships)
## Logging Configuration
This section is used to manage how logging messages are processed.
To turn off logging notifications, change the `handlers` `notifications` `class` to `logging.NullHandler`
## Danger Zone
Everything below logging is magic. **Do not touch.**

View File

@@ -0,0 +1,81 @@
# Supervisor
>Supervisor is a client/server system that allows its users to control a number of processes on UNIX-like operating systems.
What that means is supervisor will take care of ensuring the celery workers are running (and mumble authenticator) and start the automatically on reboot. Handy, eh?
## Installation
Most OSes have a supervisor package available in their distribution.
Ubuntu:
sudo apt-get install supervisor
CentOS:
sudo yum install supervisor
sudo systemctl enable supervisord.service
sudo systemctl start supervisord.service
## Configuration
Auth provides example config files for the celery workers, the periodic task scheduler (celery beat), and the mumble authenticator. All of these are available in `thirdparty/Supervisor`.
For most users, all you have to do is copy the config files to `/etc/supervisor/conf.d` then restart the service. Copy `auth.conf` for the celery workers, and `auth-mumble.conf` for the mumble authenticator. For all three just use a wildcard:
sudo cp thirdparty/Supervisor/* /etc/supervisor/conf.d
Ubuntu:
sudo service supervisor restart
CentOS:
sudo systemctl restart supervisor.service
## Checking Status
To ensure the processes are working, check their status:
sudo supervisorctl status
Processes will be `STARTING`, `RUNNING`, or `ERROR`. If an error has occurred, check their log files:
- celery workers: `log/worker.log`
- celery beat: `log/beat.log`
- authenticator: `log/authenticator.log`
## Restarting Processes
To restart the celery group:
sudo supervisorctl restart auth:*
To restart just celerybeat:
sudo supervisorctl restart auth:celerybeat
To restart just celeryd:
sudo supervisorctl restart auth:celeryd
To restart just mumble authenticator:
sudo supervisorctl restart auth-mumble
## Customizing Config Files
The only real customization needed is if running in a virtual environment. The python path will have to be changed in order to start in the venv.
Edit the config files and find the line saying `command`. Replace `python` with `/path/to/venv/bin/python`. For Celery replace `celery` with `/path/to/venv/bin/celery`. This can be relative to the `directory` specified in the config file.
Note that for config changes to be loaded, the supervisor service must be restarted.
## Troubleshooting
### auth-celerybeat fails to start
Most often this is caused by a permissions issue on the allianceauth directory (the error will talk about `celerybeat.pid`). The easiest fix is to edit its config file and change the `user` from `allianceserver` to `root`.
### Workers are using old settings
Every time the codebase is updated or settings file changed, workers will have to be restarted. Easiest way is to restart the supervisor service (see configuration above for commands)

View File

@@ -0,0 +1,75 @@
# Ubuntu Installation
Its recommended to update all packages before proceeding.
sudo apt-get update
sudo apt-get upgrade
sudo reboot
Now install all [dependencies](dependencies.md). For this guide you'll need the optional [Apache section](dependencies.md) as well.
sudo apt-get install xxxxxxx
replacing the xs with the list of packages.
For security and permissions, its highly recommended you create a user to install under who is not the root account.
sudo adduser allianceserver
This user needs sudo powers. Add them by editing the sudoers file:
sudo nano /etc/sudoers
Find the line which says `root ALL=(ALL:ALL) ALL` - beneath it add another line `allianceserver ALL=(ALL:ALL) ALL` - now reboot.
**From this point on you need to be logged in as the allianceserver user**
AllianceAuth needs a MySQL user account. Create one as follows, replacing `PASSWORD` with an actual secure password:
mysql -u root -p
CREATE USER 'allianceserver'@'localhost' IDENTIFIED BY 'PASSWORD';
GRANT ALL PRIVILEGES ON * . * TO 'allianceserver'@'localhost';
Now we need to make the requisite databases.
create database alliance_auth;
create database alliance_forum;
create database alliance_jabber;
create database alliance_mumble;
Ensure you are in the allianceserver home directory by issuing `cd`
Now we clone the source code:
git clone https://github.com/allianceauth/allianceauth.git
Enter the folder by issuing `cd allianceauth`
Ensure you're on the latest version with the following:
git checkout v1.15.7
Python package dependencies can be installed from the requirements file:
sudo pip install -r requirements.txt
The settings file needs configuring. See [this lengthy guide](settings.md) for specifics.
Django needs to install models to the database before it can start.
python manage.py migrate
AllianceAuth needs to generate corp and alliance models before it can assign users to them.
python manage.py shell < run_alliance_corp_update.py
Now we need to round up all the static files required to render templates. Answer yes when prompted.
python manage.py collectstatic
Test the server by starting it manually.
python manage.py runserver 0.0.0.0:8000
If you see an error, stop, read it, and resolve it. If the server comes up and you can access it at `yourip:8000`, you're golden. It's ok to stop the server if you're going to be installing apache.
Once installed, follow the [Quick Start Guide](quickstart.md)

View File

@@ -0,0 +1,10 @@
# Installation
```eval_rst
.. toctree::
:maxdepth: 2
auth/index
services/index
```

View File

@@ -0,0 +1,50 @@
# Discord
## Overview
Discord is a web-based instant messaging client with voice. Kind of like teamspeak meets slack meets skype. It also has a standalone app for phones and desktop.
## Setup
Add `services.modules.discord` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
### Creating a Server
*If you already have a Discord server, skip the creation step, but be sure to retrieve the server ID and enter it in settings.py*
Navigate to the [Discord site](https://discordapp.com/) and register an account, or log in if you have one already.
On the left side of the screen youll see a circle with a plus sign. This is the button to create a new server. Go ahead and do that, naming it something obvious.
Now retrieve the server ID from the URL of the page youre on. The ID is the first of the very long numbers. For instance my testing servers url look like:
https://discordapp.com/channels/120631096835571712/120631096835571712
with a server ID of `120631096835571712`
Update settings.py, inputting the server ID as `DISCORD_GUILD_ID`
### Registering an Application
Navigate to the [Discord Developers site.](https://discordapp.com/developers/applications/me) Press the plus sign to create a new application.
Give it a name and description relating to your auth site. Add a redirect to `https://example.com/discord/callback/`, substituting your domain. Press Create Application.
Update settings.py, inputting this redirect address as `DISCORD_CALLBACK_URL`
On the application summary page, press Create a Bot User.
Update settings.py with these pieces of information from the summary page:
- From the App Details panel, `DISCORD_APP_ID` is the Client/Application ID
- From the App Details panel, `DISCORD_APP_SECRET` is the Secret
- From the App Bot Users panel, `DISCORD_BOT_TOKEN` is the Token
### Adding a Bot to the Server
Once created, navigate to the services page of your AllianceAuth install as the superuser account. At the top there is a big green button labelled Link Discord Server. Click it, then from the drop down select the server you created, and then Authorize.
This adds a new user to your Discord server with a `BOT` tag, and a new role with the same name as your Discord application. Don't touch either of these. If for some reason the bot loses permissions or is removed from the server, click this button again.
To manage roles, this bot role must be at the top of the hierarchy. Edit your Discord server, roles, and click and drag the role with the same name as your application to the top of the list. This role must stay at the top of the list for the bot to work. Finally, the owner of the bot account must enable 2 Factor Authentication (this is required from discord for kicking and modifying member roles). If you are unsure what 2FA is or how to set it up, refer to [this support page](https://support.discordapp.com/hc/en-us/articles/219576828). It is also recommended to force 2fa on your server (this forces any admins or moderators to have 2fa enabled to perform similar functions on discord).
### Linking Accounts
Instead of the usual account creation procedure, for Discord to work we need to link accounts to AllianceAuth. When attempting to enable the Discord service, users are redirected to the official Discord site to authenticate. They will need to create an account if they don't have one prior to continuing. Upon authorization, users are redirected back to AllianceAuth with an OAuth code which is used to join the Discord server.
## Managing Roles
Once users link their accounts youll notice Roles get populated on Discord. These are the equivalent to Groups on every other service. The default permissions should be enough for members to chat and use comms. Add more permissions to the roles as desired through the server management window.

View File

@@ -0,0 +1,182 @@
# Discourse
Add `services.modules.discourse` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
## Install Docker
wget -qO- https://get.docker.io/ | sh
### Get docker permissions
sudo usermod -aG docker allianceserver
Logout, then back in for changes to take effect.
## Install Discourse
### Download Discourse
sudo mkdir /var/discourse
sudo git clone https://github.com/discourse/discourse_docker.git /var/discourse
### Configure
cd /var/discourse
sudo cp samples/standalone.yml containers/app.yml
sudo nano containers/app.yml
Change the following:
- `DISCOURSE_DEVELOPER_EMAILS` should be a list of admin account email addresses separated by commas
- `DISCOUSE_HOSTNAME` should be 127.0.0.1
- Everything with `SMTP` depends on your mail settings. Account created through auth do not require email validation, so to ignore everything email (NOT RECOMMENDED), just change the SMTP address to something random so it'll install. Note that not setting up email means any password resets emails won't be sent, and auth cannot reset these. [There are plenty of free email services online recommended by Discourse.](https://github.com/discourse/discourse/blob/master/docs/INSTALL-email.md#recommended-email-providers-for-discourse)
To install behind apache, look for this secion:
...
## which TCP/IP ports should this container expose?
expose:
- "80:80" # fwd host port 80 to container port 80 (http)
...
Change it to this:
...
## which TCP/IP ports should this container expose?
expose:
- "7890:80" # fwd host port 7890 to container port 80 (http)
...
Or any other port will do, if taken. Remember this number.
### Build and launch
sudo nano /etc/default/docker
Uncomment this line:
DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4"
Restart docker:
sudo service docker restart
Now build:
sudo ./launcher bootstrap app
sudo ./launcher start app
#### Errors:
in case you run into not enough RAM for the docker bootstraping you might want to consider using `./discourse-setup` command. It will start bootstraping and is going to create the `/containers/app.yml` which you can edit.
Note: every time you change something in the `app.yml` you must bootstrap again which will take between *2-8 minutes* and is accomplished by `./launcher rebuild app`.
***
## Apache config
Discourse must run on its own subdomain - it can't handle routing behind an alias like '/forums'. To do so, make a new apache config:
sudo nano /etc/apache2/sites-available/discourse.conf
And enter the following, changing the port if you used a different number:
<VirtualHost *:80>
ServerName discourse.example.com
ProxyPass / http://0.0.0.0:7890/
ProxyPassReverse / http://0.0.0.0:7890/
</VirtualHost>
Now enable proxies and restart apache:
sudo a2ensite discourse
sudo a2enmod proxy_http
sudo service apache2 reload
### Setting up SSL
It is 2017 and there is no reason why you should not setup a SSL certificate and enforce https. You may want to consider certbot with Let's encrypt: https://www.digitalocean.com/community/tutorials/how-to-secure-apache-with-let-s-encrypt-on-ubuntu-16-04
sudo certbot --apache -d example.com
now adapt the apache configuration:
sudo nano /etc/apache2/sites-enabled/discourse.conf
and adapt it followlingly:
<VirtualHost *:80>
ServerName discourse.example.com
RewriteEngine on
RewriteCond %{SERVER_NAME} =discourse.example.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
Then adapt change the ssl-config file:
sudo nano /etc/apache2/sites-enabled/discourse-le-ssl.conf
and adapt it followlingly:
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName discourse.example.com
ProxyPass / http://127.0.0.1:7890/
ProxyPassReverse / http://127.0.0.1:7890/
ProxyPreserveHost On
RequestHeader set X-FORWARDED-PROTOCOL https
RequestHeader set X-FORWARDED-SSL on
SSLCertificateFile /etc/letsencrypt/live/discourse.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/discourse.example.com/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>
make sure that `a2enmod headers` is enabled and run:
sudo service apache2 restart
Now you are all set-up and can even enforce https in discourse settings.
## Configure API
### Generate admin account
From the /var/discourse folder,
./launcher enter app
rake admin:create
Follow prompts, being sure to answer `y` when asked to allow admin privileges.
### Create API key
Navigate to `discourse.example.com` and log on. Top right press the 3 lines and select `Admin`. Go to API tab and press `Generate Master API Key`.
Now go to the allianceauth folder and edit settings:
nano /home/allianceserver/allianceauth/alliance_auth/settings.py
Scroll down to the Discourse section and set the following:
- `DISCOURSE_URL`: `discourse.example.com`
- `DISCOURSE_API_USERNAME`: the username of the admin account you generated the API key with
- `DISCOURSE_API_KEY`: the key you just generated
***
### Configure SSO
Navigate to `discourse.example.com` and log in. Back to the admin site, scroll down to find SSO settings and set the following:
- `enable_sso`: True
- `sso_url`: `http://example.com/discourse/sso`
- `sso_secret`: some secure key
Save, now change settings.py and add the following:
- `DISCOURSE_SSO_SECRET`: the secure key you just set
### Enable for your members
Assign discourse permissions for each auth-group that should have access to discourse.
You might want to setup Read/Write/Delete rights per Auth group in discourse as you can limit which categories shall be accessablie per auth-group.
## Done

View File

@@ -0,0 +1,20 @@
# Services
```eval_rst
.. toctree::
permissions
market
discord
discourse
ipboard3
mumble
openfire
phpbb3
smf
teamspeak3
xenforo
jacknife
pathfinder
```

View File

@@ -0,0 +1,42 @@
# IPBoard3
Add `services.modules.ipboard` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
Youre on your own for the initial install of IPBoard. Its pretty much just download, unzip, and move to `/var/www/ipboard/`. Make sure to
sudo chown -R www-data:www-data /var/www/ipboard
a few times because its pretty finicky.
Youll need to add another alias in your apache config, this one for `/ipboard` pointing to `/var/www/ipboard` and add another `<directory>` block for `/var/www/ipboard` with `Require all granted` or `Allow from all` depending on your apache version.
IPBoard needs a database table. Log in to mysql and run:
create database alliance_ipboard;
Thats all for SQL work. Control+D to close.
Navigate to http://example.com/ipboard and proceed with the install. If it whines about permissions make sure to `chown` again. Point it at that database we just made, using the `allianceserver` MySQL user account from the full install.
Once you get everything installed we need to copy the api module folder
sudo cp -a /home/allianceserver/allianceauth/thirdparty/IPBoard3/aa /var/www/ipboard/interface/board/modules/aa
and again run that `chown` command.
Log into the AdminCP for IPBoard and find your way to the `System` tab. On the left navigation bar, under `Tools and Settings`, select `API Users`.
Enable the API by toggling the `XML-RPC Status` from `disabled` to `enabled` (red box, top right of the page) and save. Now create a new api user. Put something descriptive for title such as AllianceAuth, then on the bottom panel click the `AllianceAuth` tab and tick all the boxes. Press `Create New API User` to save it.
Copy the API key. Now edit your settings.py as follows:
- IPBOARD_APIKEY is the key you just copied
- IPBOARD_ENDPOINT is `http://example.com/ipboard/interface/board/index.php`
Now enable IPBoard for Auth and/or Blue by editing the auth settings.
Save and exit. Restart apache or gunicorn.
Test it by creating a user through Alliance Auth. Just note right now theres no real error handling, so if account creation fails itll still return a username/password combo.
Good luck!

View File

@@ -0,0 +1,71 @@
# Eve Jacknife
## Overview
Eve Jacknife is used to audit an api so that you might see character skills and what ships they can fly, mails, contracts,assets, and any other given access from a specific api key.
## Dependencies
All php and mysql dependencies should have been taken care of during setup.
## Installation
### Get Code
Navigate to your server's web directory: `cd /var/www`
Download the code: `sudo git clone https://github.com/whinis/eve-jacknife`
### Create Database
mysql -u root -p -e "create database jackknife; grant all privileges on jackknife.* to 'allianceserver'@'localhost';"
### Configure Settings
Change directory to jacknife: `cd eve-jacknife`
Now copy the template: `sudo cp base.config.php eve.config.php`
And now edit: `sudo nano eve.config.php`
Add the database user information:
- `$sql_u = "allianceserver"`
- `$sql_p = "MY_SQL_PASSWORD_HERE"`
## Apache Configuration
Change ownership of the directory: `sudo chown -R www-data:www-data ../eve-jacknife`
Eve Jacknife can be served two ways: on its own subdomain (`jacknife.example.com`) or as an alias (`example.com/jacknife`)
### Subdomain
As its own subdomain, create a new apache config: `sudo nano /etc/apache2/sites-available/jacknife.conf` and enter the following:
<VirtualHost *:80>
DocumentRoot "/var/www/eve-jacknife"
ServerName jacknife.example.com
<Directory "/var/www/eve-jacknife">
Require all granted
AllowOverride all
DirectoryIndex index.php
</Directory>
</VirtualHost>
Enable the new site with `sudo a2ensite jacknife.conf` and then reload apache with `sudo service apache2 reload`
### Alias
As an alias, edit your site config (usually 000-default): `sudo nano etc/apache2/sites-available/000-default.conf` and add the following inside the `VirtualHost` block:
Alias /jacknife "/var/www/eve-jacknife/"
<Directory "/var/www/eve-jacknife">
Require all granted
DirectoryIndex index.php
</Directory>
Reload apache to take effect: `sudo service apache2 reload`
## Install SQL
Once apache is configured, Eve Jacknife needs to install some data. Navigate to it in your browser and append `/Installer.php` to the URL.
Enter your database password and press Check. If all the boxes come back green press Save. On the next screen press Install and wait for it to finish.
## Update Auth Settings
Edit your aut settings file (`nano ~/allianceauth/alliance_auth/settings.py`) and replace `API_KEY_AUDIT_URL` with either `http://jacknife.example.com/?usid={api_id}&apik={vcode}` or `http://example.com/jacknife/?usid={api_id}&apik={vcode}` depending on your apache choice.

View File

@@ -0,0 +1,106 @@
# Alliance Market
Add `services.modules.market` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
Alliance Market needs a database. Create one in mysql. Default name is `alliance_market`:
mysql -u root -p
create database alliance_market;
exit;
To clone the repo, install packages:
sudo apt-get install mercurial meld
Change to the web folder:
cd /var/www
Now clone the repo
sudo hg clone https://bitbucket.org/krojew/evernus-alliance-market
Make cache and log directories
sudo mkdir evernus-alliance-market/app/cache
sudo mkdir evernus-alliance-market/app/logs
sudo chmod -R 777 evernus-alliance-market/app/cache
sudo chmod -R 777 evernus-alliance-market/app/logs
Change ownership to apache
sudo chown -R www-data:www-data evernus-alliance-market
Enter
cd evernus-alliance-market
Set environment variable
export SYMFONY_ENV=prod
Copy configuration
sudo cp app/config/parameters.yml.dist app/config/parameters.yml
Edit, changing the following:
- `database_name` to `alliance_market`
- `database_user` to your MySQL user (usually `allianceserver`)
- `database_password` to your MySQL user password
- email settings, eg gmail
Edit `app/config/config.yml` and add the following:
services:
fos_user.doctrine_registry:
alias: doctrine
Install composer [as per these instructions.](https://getcomposer.org/download/)
Update dependencies.
sudo php composer.phar update --optimize-autoloader
Prepare the cache:
sudo php app/console cache:clear --env=prod --no-debug
Dump assets:
sudo php app/console assetic:dump --env=prod --no-debug
Create DB entries
sudo php app/console doctrine:schema:update --force
Install SDE:
sudo php app/console evernus:update:sde
Edit your apache config. Add the following:
Alias /market /var/www/evernus-alliance-market/web/
<Directory "/var/www/evernus-alliance-market/web/">
DirectoryIndex app.php
Require all granted
AllowOverride all
</Directory>
Enable rewriting
sudo a2enmod rewrite
Restart apache
sudo service apache2 reload
Once again, set cache permissions:
sudo chown -R www-data:www-data app/
Add a user account through auth, then make it a superuser:
sudo php app/console fos:user:promote your_username --super

View File

@@ -0,0 +1,88 @@
# Mumble
Add `services.modules.mumble` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
## Overview
Mumble is a free voice chat server. While not as flashy as teamspeak, it has all the functionality and is easier to customize. And is better. I may be slightly biased.
## Dependencies
The mumble server package can be retrieved from a repository we need to add, mumble/release.
sudo apt-add-repository ppa:mumble/release
sudo apt-get update
Now two packages need to be installed:
sudo apt-get install python-software-properties mumble-server
You will also need to install the python dependencies for the authenticator script:
pip install -r thirdparty/Mumble/requirements.txt
## Configuring Mumble
Mumble ships with a configuration file that needs customization. By default its located at /etc/mumble-server.ini. Open it with your favourite text editor:
sudo nano /etc/mumble-server.ini
REQUIRED: To enable the ICE authenticator, edit the following:
- `icesecretwrite=MY_CLEVER_PASSWORD`, obviously choosing a secure password
By default mumble operates on sqlite which is fine, but slower than a dedicated MySQL server. To customize the database, edit the following:
- uncomment the database line, and change it to `database=alliance_mumble`
- `dbDriver=QMYSQL`
- `dbUsername=allianceserver` or whatever you called the AllianceAuth MySQL user
- `dbPassword=` that users password
- `dbPort=3306`
- `dbPrefix=murmur_`
To name your root channel, uncomment and set `registerName=` to whatever cool name you want
Save and close the file (control + O, control + X).
To get mumble superuser account credentials, run the following:
sudo dpkg-reconfigure mumble-server
Set the password to something youll remember and write it down. This is needed to manage ACLs.
Now restart the server to see the changes reflected.
sudo service mumble-server restart
Thats it! Your server is ready to be connected to at example.com:64738
## Configuring the Authenticator
The ICE authenticator lives in `allianceauth/thirdparty/Mumble/`, cd to this directory.
Make a copy of the default config:
cp authenticator.ini.example authenticator.ini
Edit `authenticator.ini` and change these values:
- `[database]`
- `user = ` your allianceserver MySQL user
- `password = ` your allianceserver MySQL user's password
- `[ice]`
- `secret = ` the `icewritesecret` password set earlier
Test your configuration by starting it: `python authenticator.py`
## Running the Authenticator
The authenticator needs to be running 24/7 to validate users on Mumble. The best way is to run it in a screen much like celery:
screen -dm bash -c 'python authenticator.py'
Much like celery tasks, this process needs to be started every time the server reboots. It needs to be launched from this directory, so cd to this folder to launch.
Note that groups will only be created on Mumble automatically when a user joins who is in the group.
## Making and Managing Channels
ACL is really above the scope of this guide. Once AllianceAuth creates your groups, go ahead and follow one of the wonderful web guides available on how to set up channel ACL properly.
## Setup Complete
Youve finished the steps required to make AllianceAuth work with Mumble. Play around with it and make it your own.

View File

@@ -0,0 +1,103 @@
# Openfire
Add `services.modules.openfire` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
## Overview
Openfire is a java-based xmpp server (jabber).
## Dependencies
One additional package is required - [openjdk8](http://askubuntu.com/questions/464755/how-to-install-openjdk-8-on-14-04-lts)
sudo add-apt-repository ppa:webupd8team/java -y
sudo apt-get update
sudo apt-get install oracle-java8-installer
## Setup
### Download Installer
Openfire is not available through repositories so we need to get a debian from the developer.
On your PC, naviage to the [Ignite Realtime downloads section](https://www.igniterealtime.org/downloads/index.jsp), and under Openfire select Linux, click on the debian file (2nd from bottom of list, ends with .deb).
Retrieve the file location by copying the url from the “click here” link.
In the console, ensure youre in your users home directory: `cd ~`
Now download the package. Replace the link below with the link you got earlier.
wget https://www.igniterealtime.org/downloadServlet?filename=openfire/openfire_4.1.1_all.deb
Now install from the debian. Replace the filename with your file name (the last part of the download url is the file name)
sudo dpkg -i openfire_4.1.1_all.deb
### Web Configuration
The remainder of the setup occurs through Openfires web interface. Navigate to http://example.com:9090, or if youre behind CloudFlare, go straight to your servers IP:9090.
Select your language. I sure hope its english if youre reading this guide.
Under Server Settings, set the Domain to `example.com` replacing it with your actual domain. Dont touch the rest.
Under Database Settings, select `Standard Database Connection`
On the next page, select `MySQL` from the dropdown list and change the following:
- `[server]` is replaced by `127.0.0.1`
- `[database]` is replaced by the name of the database to be used by Openfire
- enter the MySQL username you created for AllianceAuth, usually `allianceserver`
- enter the MySQL password for this user
If Openfire returns with a failed to connect error, re-check these settings. Note the lack of square brackets.
Under Profile Settings, leave `Default` selected.
Create an administrator account. The actual name is irrelevant, just dont lost this login information.
Finally, log in to the console with your admin account.
### REST API Setup
Navigate to the `plugins` tab, and then `Available Plugins` on the left navigation bar. Youll need to fetch the list of available plugins by clicking the link.
Once loaded, press the green plus on the right for `REST API`.
Navigate the `Server` tab, `Sever Settings` subtab. At the bottom of the left navigation bar select `REST API`.
Select `Enabled`, and `Secret Key Auth`. Update Alliance Auth settings with this secret key as `OPENFIRE_SECRET_KEY`.
### Broadcast Plugin Setup
Navigate to the `Users/Groups` tab and select `Create New User` from the left navigation bar.
Username is what you set in `BROADCAST_USER` without the @ sign, usually `broadcast`.
Password is what you set in `BROADCAST_USER_PASSWORD`
Press `Create User` to save this user.
Broadcasting requires a plugin. Navigate to the `plugins` tab, press the green plus for the `Broadcast` plugin.
Navigate to the `Server` tab, `Server Manager` subtab, and select `System Properties`. Enter the following:
- Name: `plugin.broadcast.disableGroupPermissions`
- Value: `True`
- Do not encrypt this property value
- Name: `plugin.broadcast.allowedUsers`
- Value: `broadcast@example.com`, replacing the domain name with yours
- Do not encrypt this property value
If you have troubles getting broadcasts to work, you can try setting the optional (you will need to add it) `BROADCAST_IGNORE_INVALID_CERT` setting to `True`. This will allow invalid certificates to be used when connecting to the Openfire server to send a broadcast.
### Group Chat
Channels are available which function like a chat room. Access can be controlled either by password or ACL (not unlike mumble).
Navigate to the `Group Chat` tab and select `Create New Room` from the left navigation bar.
- Room ID is a short, easy-to-type version of the rooms name users will connect to
- Room Name is the full name for the room
- Description is short text describing the rooms purpose
- Set a password if you want password authentication
- Every other setting is optional. Save changes.
Now select your new room. On the left navigation bar, select `Permissions`.
ACL is achieved by assigning groups to each of the three tiers: `Owners`, `Admins` and `Members`. `Outcast` is the blacklist. Youll usually only be assigning groups to the `Member` category.
## Setup Complete
Youve finished the steps required to make AllianceAuth work with Openfire. Play around with it and make it your own.

View File

@@ -0,0 +1,77 @@
# Pathfinder
Pathfinder is a wormhole mapping tool.
While auth doesn't integrate with pathfinder anymore, from personal experience I've found it much easier to use the following install process than to try and follow the pathfinder-supplied docs.
## Installation
### Get the code
Navigate to the install location: `cd /var/www/` and git clone the repo:
sudo git clone https://github.com/exodus4d/pathfinder.git
### Create logs and caches
Change directory to pathfinder: `cd pathfinder`
The logging and caching folders need to be created and have permission set. If upon installation you get Server Error 500, try resetting these permissions.
sudo mkdir logs
sudo mkdir tmp/cache
sudo chmod -R 766 logs
sudo chmod -R 766 tmp/cache
## .htaccess Configuration
In your `pathfinder` directory there are two `.htaccess` files. The default installation instructions want you to choose one for rewriting purposes, and these force you to www.pathfinder.example.com. Personally I don't like that.
So we'll frankenstein our own. We'll use the HTTP one as a base:
sudo mv .htaccess .htaccess_HTTPS
sudo mv .htaccess_HTTPS .htaccess
sudo nano .htaccess
Find the www rewriting section (labelled `Rewrite NONE www. to force www.`). Change it so that all lines start with a `#`:
#RewriteCond %{HTTP_HOST} !^www\.
# skip "localhost" (dev environment)...
#RewriteCond %{HTTP_HOST} !=localhost
# skip IP calls (dev environment) e.g. 127.0.0.1
#RewriteCond %{HTTP_HOST} !^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$
# rewrite everything else to "http://" and "www."
#RewriteRule .* http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
This allows us to choose SSL and www forwarding with our apache conf instead of this htaccess file.
## Apache Configuration
The best way to have this is to setup a subdomain on your server.
Create a config file `sudo nano /etc/apache2/sites-available/pathfinder.conf` and enter [this configuration](http://pastebin.com/wmXyf6pN), being sure to edit the `ServerName`
Enable it with:
sudo a2ensite pathfinder.conf
sudo service apache2 reload
## Configuration Files
The default configuration should be fine in most cases. Edit all values with caution!
environment.ini
- `SERVER` Should be changed to `PRODUCTION`
- `BASE` is the full filesystem path to the application root on your server. In our case, `/var/www/pathfinder/`
- `URL` Is the URL to your app (without a trailing slash). In our case, `http://pathfinder.example.com`
- `DEBUG` sets the level of debugging (1,2 or 3) (check /logs for a more detail backtrace information)
- `DB_*` sets your DB connection information
- `SMTP_*` are used to send out emails, you will need an SMTP server login to make this work. (not required)
- `SSO_CCP_*` follow the [official docs](https://github.com/exodus4d/pathfinder/wiki/CREST)
## Database Setup
This is done through the browser. Go to `pathfinder.example.com/setup` and see the [official docs](https://github.com/exodus4d/pathfinder/wiki/Database) for instructions.
## Cron Jobs
Again the [official docs](https://github.com/exodus4d/pathfinder/wiki/Cronjob) do a good job here.
## Finish Setup
Once you've compelted the above steps, we need to disable the setup page. Edit the routes with `nano app/routes.ini` and put a `;` in front of the line starting with `GET @setup`

View File

@@ -0,0 +1,37 @@
# Service Permissions
```eval_rst
.. note::
New in 1.15
```
In the past, access to services was dictated by a list of settings in `settings.py`, granting access to each particular service for Members and/or Blues. This meant that granting access to a service was very broad and rigidly structured around these two states.
## Permissions based access
Instead of granting access to services by the previous rigid structure, access to services is now granted by the built in Django permissions system. This means that service access can be more granular, allowing only certain groups, for instance Corp CEOs, or even individual user access to each enabled service.
```eval_rst
.. important::
If you grant access to an individual user, they will have access to that service regardless of whether or not they are a member.
```
Each service has an access permission defined, named like `Can access the <service name> service`.
To mimick the old behaviour of enabling services for all members, you would select the `Member` group from the admin panel, add the required service permission to the group and save. Likewise for Blues, select the `Blue` group and add the required permission.
A user can be granted the same permission from multiple sources. e.g. they may have it granted by several groups and directly granted on their account as well. Auth will not remove their account until all instances of the permission for that service have been revoked.
## Removing access
```eval_rst
.. danger::
Access removal is processed immediately after removing a permission from a user or group. If you remove access from a large group, such as Member, it will immediately remove all users from that service.
```
When you remove a service permission from a user, a signal is triggered which will activate an immediate permission check. For users this will trigger an access check for all services. For groups, due to the potential extra load, only the services whose permissions have changed will be verified, and only the users in that group.
If a user no longer has permission to access the service when this permissions check is triggered, that service will be immediately disabled for them.
### Disabling user accounts
When you unset a user as active in the admin panel, all of that users service accounts will be immediately disabled or removed. This is due to the built in behaviour of Djangos permissions system, which will return False for all permissions if a users account is disabled, regardless of their actual permissions state.

View File

@@ -0,0 +1,71 @@
# phpBB3
Add `services.modules.phpbb3` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
## Overview
phpBB is a free php-based forum. Its the default forum for AllianceAuth.
## Dependencies
All dependencies should have been taken care of during setup.
## Setup
### Download Phpbb3
phpBB is available as a zip from their website. Navigate to the websites [downloads section](https://www.phpbb.com/downloads/) using your PC browser and copy the URL for the latest version zip.
In the console, navigate to your users home directory: `cd ~`
Now download using wget, replacing the url with the url for the package you just retrieved
wget https://www.phpbb.com/files/release/phpBB-3.2.0.zip
This needs to be unpackaged. Unzip it, replacing the file name with that of the file you just downloaded
unzip phpBB-3.2.0.zip
Now we need to move this to our web directory. Usually `/var/www/forums`.
sudo mv phpBB3 /var/www/forums
The web server needs read/write permission to this folder
sudo chown -R www-data:www-data /var/www/forums
### Web Install
Navigate to http://example.com/forums where you will be presented with an installer.
Click on the `Install` tab.
All the requirements should be met. Press `Start Install`.
Under Database Settings, set the following:
- Database Type is `MySQL`
- Database Server Hostname is `127.0.0.1`
- Database Server Port is left blank
- Database Name is `alliance_forum`
- Database Username is your MySQL user for AllianceAuth, usually `allianceserver`
- Database Password is this users password
You should see `Succesful Connection` and proceed.
Enter administrator credentials on the next page.
Everything from here should be intuitive.
phpBB will then write its own config file.
### Open the Forums
Before users can see the forums, we need to remove the install directory
sudo rm -rf /var/www/forums/install
### Enabling Avatars
AllianceAuth sets user avatars to their character portrait when the account is created or password reset. We need to allow external URLs for avatars for them to behave properly. Navigate to the admin control panel for phpbb3, and under the `General` tab, along the left navigation bar beneath `Board Configuration`, select `Avatar Settings`. Set `Enable Remote Avatars` to `Yes` and then `Submit`.
![location of the remote avatar setting](http://i.imgur.com/eWrotRX.png)
You can allow members to overwrite the portrait with a custom image if desired. Navigate to `Users and Groups`, `Group Permissions`, select the appropriate group (usually `Member` if you want everyone to have this ability), expand `Advanced Permissions`, under the `Profile` tab, set `Can Change Avatars` to `Yes`, and press `Apply Permissions`.
![location of change avatar setting](http://i.imgur.com/Nc6Rzo9.png)
## Setup Complete
Youve finished the steps required to make AllianceAuth work with phpBB. Play around with it and make it your own.

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