You've already forked Mailu
							
							
				mirror of
				https://github.com/Mailu/Mailu.git
				synced 2025-10-30 23:37:43 +02:00 
			
		
		
		
	Implement admin and manager management
This commit is contained in:
		| @@ -21,7 +21,8 @@ def inject_user(): | ||||
|  | ||||
| # Import views | ||||
| from freeposte.admin.views import \ | ||||
|     administrators, \ | ||||
|     admins, \ | ||||
|     managers, \ | ||||
|     base, \ | ||||
|     aliases, \ | ||||
|     users, \ | ||||
|   | ||||
| @@ -61,3 +61,13 @@ class AliasForm(Form): | ||||
|     destination = fields.StringField('Destination') | ||||
|     comment = fields.StringField('Comment') | ||||
|     submit = fields.SubmitField('Create') | ||||
|  | ||||
|  | ||||
| class AdminForm(Form): | ||||
|     admin = fields.StringField('Admin address', [validators.Email()]) | ||||
|     submit = fields.SubmitField('Submit') | ||||
|  | ||||
|  | ||||
| class ManagerForm(Form): | ||||
|     manager = fields.StringField('Manager address', [validators.Email()]) | ||||
|     submit = fields.SubmitField('Submit') | ||||
|   | ||||
| @@ -7,15 +7,10 @@ from datetime import datetime | ||||
| import re | ||||
|  | ||||
|  | ||||
| # Many-to-many association table for domain administrators | ||||
| admins = db.Table('admin', | ||||
| # Many-to-many association table for domain managers | ||||
| managers = db.Table('manager', | ||||
|     db.Column('domain_name', db.String(80), db.ForeignKey('domain.name')), | ||||
|     db.Column('user_domain_name', db.String(80)), | ||||
|     db.Column('user_localpart', db.String(80)), | ||||
|     db.ForeignKeyConstraint( | ||||
|         ('user_domain_name', 'user_localpart'), | ||||
|         ('user.domain_name', 'user.localpart') | ||||
|     ) | ||||
|     db.Column('user_address', db.String(80), db.ForeignKey('user.address')) | ||||
| ) | ||||
|  | ||||
|  | ||||
| @@ -34,8 +29,8 @@ class Domain(Base): | ||||
|     """ A DNS domain that has mail addresses associated to it. | ||||
|     """ | ||||
|     name = db.Column(db.String(80), primary_key=True, nullable=False) | ||||
|     admins = db.relationship('User', secondary=admins, | ||||
|         backref=db.backref('admin_of'), lazy='dynamic') | ||||
|     managers = db.relationship('User', secondary=managers, | ||||
|         backref=db.backref('manager_of'), lazy='dynamic') | ||||
|     max_users = db.Column(db.Integer, nullable=False, default=0) | ||||
|     max_aliases = db.Column(db.Integer, nullable=False, default=0) | ||||
|  | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| {% extends "working.html" %} | ||||
							
								
								
									
										23
									
								
								admin/freeposte/admin/templates/admin/create.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								admin/freeposte/admin/templates/admin/create.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% block title %} | ||||
| Add a global administrator | ||||
| {% endblock %} | ||||
|  | ||||
| {% block box_content %} | ||||
| <form class="form" method="post" role="form"> | ||||
|   {{ form.hidden_tag() }} | ||||
|   {{ macros.form_field(form.admin, id='admin') }} | ||||
|   {{ macros.form_field(form.submit) }} | ||||
|   <script> | ||||
|     $("#admin").tagsinput({ | ||||
|       confirmKeys: [9, 13, 32], | ||||
|       tagClass: 'label label-primary large', | ||||
|       typeahead: { | ||||
|         afterSelect: function(val) { this.$element.val(""); }, | ||||
|         source: {{ current_user.get_managed_addresses()|map('string')|list|tojson }} | ||||
|       } | ||||
|     }); | ||||
|   </script> | ||||
| </form> | ||||
| {% endblock %} | ||||
							
								
								
									
										32
									
								
								admin/freeposte/admin/templates/admin/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								admin/freeposte/admin/templates/admin/list.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% block title %} | ||||
| Global administrators | ||||
| {% endblock %} | ||||
|  | ||||
| {% block main_action %} | ||||
| <a class="btn btn-primary" href="{{ url_for('.admin_create') }}">Add administrator</a> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block box %} | ||||
| <table class="table table-bordered"> | ||||
|   <tbody> | ||||
|     <tr> | ||||
|       <th>Actions</th> | ||||
|       <th>Address</th> | ||||
|       <th>Created</th> | ||||
|       <th>Last edit</th> | ||||
|     </tr> | ||||
|     {% for admin in admins %} | ||||
|     <tr> | ||||
|       <td> | ||||
|         <a href="{{ url_for('.admin_delete', admin=admin.address) }}" onclick="return confirm('Are you sure?')" title="Delete"><i class="fa fa-trash"></i></a> | ||||
|       </td> | ||||
|       <td>{{ admin }}</td> | ||||
|       <td>{{ admin.created_at }}</td> | ||||
|       <td>{{ admin.updated_at or '' }}</td> | ||||
|     </tr> | ||||
|     {% endfor %} | ||||
|   </tbody> | ||||
| </table> | ||||
| {% endblock %} | ||||
| @@ -32,7 +32,7 @@ Domain list | ||||
|       <td> | ||||
|         <a href="{{ url_for('.user_list', domain_name=domain.name) }}" title="Users"><i class="fa fa-envelope-o"></i></a>  | ||||
|         <a href="{{ url_for('.alias_list', domain_name=domain.name) }}" title="Aliases"><i class="fa fa-at"></i></a>  | ||||
|         <a href="{{ url_for('.domain_admins', domain_name=domain.name) }}" title="Administrators"><i class="fa fa-user"></i></a>  | ||||
|         <a href="{{ url_for('.manager_list', domain_name=domain.name) }}" title="Managers"><i class="fa fa-user"></i></a>  | ||||
|       </td> | ||||
|       <td>{{ domain.name }}</td> | ||||
|       <td>{{ domain.users | count }} / {{ domain.max_users or '∞' }}</td> | ||||
|   | ||||
							
								
								
									
										27
									
								
								admin/freeposte/admin/templates/manager/create.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								admin/freeposte/admin/templates/manager/create.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% block title %} | ||||
| Add a manager | ||||
| {% endblock %} | ||||
|  | ||||
| {% block subtitle %} | ||||
| {{ domain }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block box_content %} | ||||
| <form class="form" method="post" role="form"> | ||||
|   {{ form.hidden_tag() }} | ||||
|   {{ macros.form_field(form.manager, id='manager') }} | ||||
|   {{ macros.form_field(form.submit) }} | ||||
|   <script> | ||||
|     $("#manager").tagsinput({ | ||||
|       confirmKeys: [9, 13, 32], | ||||
|       tagClass: 'label label-primary large', | ||||
|       typeahead: { | ||||
|         afterSelect: function(val) { this.$element.val(""); }, | ||||
|         source: {{ current_user.get_managed_addresses()|map('string')|list|tojson }} | ||||
|       } | ||||
|     }); | ||||
|   </script> | ||||
| </form> | ||||
| {% endblock %} | ||||
							
								
								
									
										36
									
								
								admin/freeposte/admin/templates/manager/list.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								admin/freeposte/admin/templates/manager/list.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| {% extends "base.html" %} | ||||
|  | ||||
| {% block title %} | ||||
| Manager list | ||||
| {% endblock %} | ||||
|  | ||||
| {% block subtitle %} | ||||
| {{ domain.name }} | ||||
| {% endblock %} | ||||
|  | ||||
| {% block main_action %} | ||||
| <a class="btn btn-primary" href="{{ url_for('.manager_create', domain_name=domain.name) }}">Add manager</a> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block box %} | ||||
| <table class="table table-bordered"> | ||||
|   <tbody> | ||||
|     <tr> | ||||
|       <th>Actions</th> | ||||
|       <th>Address</th> | ||||
|       <th>Created</th> | ||||
|       <th>Last edit</th> | ||||
|     </tr> | ||||
|     {% for manager in domain.managers %} | ||||
|     <tr> | ||||
|       <td> | ||||
|         <a href="{{ url_for('.manager_delete', manager=manager.address) }}" onclick="return confirm('Are you sure?')" title="Delete"><i class="fa fa-trash"></i></a> | ||||
|       </td> | ||||
|       <td>{{ manager }}</td> | ||||
|       <td>{{ manager.created_at }}</td> | ||||
|       <td>{{ manager.updated_at or '' }}</td> | ||||
|     </tr> | ||||
|     {% endfor %} | ||||
|   </tbody> | ||||
| </table> | ||||
| {% endblock %} | ||||
| @@ -41,7 +41,7 @@ | ||||
|       </a> | ||||
|     </li> | ||||
|     <li> | ||||
|       <a href="{{ url_for('.admins') }}"> | ||||
|       <a href="{{ url_for('.admin_list') }}"> | ||||
|         <i class="fa fa-user"></i> <span>Adminitrators</span> | ||||
|       </a> | ||||
|     </li> | ||||
|   | ||||
| @@ -1,29 +0,0 @@ | ||||
| from freeposte import dockercli | ||||
| from freeposte.admin import app, db, models, forms, utils | ||||
| from flask.ext import login as flask_login | ||||
|  | ||||
| import os | ||||
| import pprint | ||||
| import flask | ||||
| import json | ||||
|  | ||||
|  | ||||
| @app.route('/services', methods=['GET']) | ||||
| @flask_login.login_required | ||||
| def services(): | ||||
|     utils.require_global_admin() | ||||
|     containers = {} | ||||
|     for brief in dockercli.containers(all=True): | ||||
|         if brief['Image'].startswith('freeposte/'): | ||||
|             container = dockercli.inspect_container(brief['Id']) | ||||
|             container['Image'] = dockercli.inspect_image(container['Image']) | ||||
|             name = container['Config']['Labels']['com.docker.compose.service'] | ||||
|             containers[name] = container | ||||
|             pprint.pprint(container) | ||||
|     return flask.render_template('admin/services.html', containers=containers) | ||||
|  | ||||
|  | ||||
| @app.route('/admins', methods=['GET']) | ||||
| @flask_login.login_required | ||||
| def admins(): | ||||
|     return flask.render_template('admin/admins.html') | ||||
							
								
								
									
										48
									
								
								admin/freeposte/admin/views/admins.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								admin/freeposte/admin/views/admins.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| from freeposte.admin import app, db, models, forms | ||||
| from flask.ext import login as flask_login | ||||
|  | ||||
| import os | ||||
| import pprint | ||||
| import flask | ||||
| import json | ||||
|  | ||||
|  | ||||
| @app.route('/admin/list', methods=['GET']) | ||||
| @flask_login.login_required | ||||
| def admin_list(): | ||||
|     admins = models.User.query.filter_by(global_admin=True) | ||||
|     return flask.render_template('admin/list.html', admins=admins) | ||||
|  | ||||
|  | ||||
| @app.route('/admin/create', methods=['GET', 'POST']) | ||||
| @flask_login.login_required | ||||
| def admin_create(): | ||||
|     form = forms.AdminForm() | ||||
|     if form.validate_on_submit(): | ||||
|         user = models.User.query.filter_by(address=form.admin.data).first() | ||||
|         if user: | ||||
|             user.global_admin = True | ||||
|             db.session.add(user) | ||||
|             db.session.commit() | ||||
|             flask.flash('User %s is now admin' % user) | ||||
|             return flask.redirect(flask.url_for('.admin_list')) | ||||
|         else: | ||||
|             flask.flash('No such user', 'error') | ||||
|     return flask.render_template('admin/create.html', form=form) | ||||
|  | ||||
|  | ||||
| @app.route('/admin/delete/<admin>', methods=['GET']) | ||||
| @flask_login.login_required | ||||
| def admin_delete(admin): | ||||
|     user = models.User.query.filter_by(address=admin).first() | ||||
|     if user: | ||||
|         user.global_admin  = False | ||||
|         db.session.add(user) | ||||
|         db.session.commit() | ||||
|         flask.flash('User %s is no longer admin' % user) | ||||
|         return flask.redirect(flask.url_for('.admin_list')) | ||||
|     else: | ||||
|         flask.flash('No such user', 'error') | ||||
|     flask.flash('Alias %s deleted' % alias) | ||||
|     return flask.redirect( | ||||
|         flask.url_for('.alias_list', domain_name=alias.domain.name)) | ||||
| @@ -1,4 +1,5 @@ | ||||
| from freeposte.admin import app, db, models, forms | ||||
| from freeposte import dockercli | ||||
| from freeposte.admin import app, db, models, forms, utils | ||||
| from flask.ext import login as flask_login | ||||
|  | ||||
| import os | ||||
| @@ -29,3 +30,17 @@ def login(): | ||||
| def logout(): | ||||
|     flask_login.logout_user() | ||||
|     return flask.redirect(flask.url_for('.index')) | ||||
|  | ||||
|  | ||||
| @app.route('/services', methods=['GET']) | ||||
| @flask_login.login_required | ||||
| def services(): | ||||
|     utils.require_global_admin() | ||||
|     containers = {} | ||||
|     for brief in dockercli.containers(all=True): | ||||
|         if brief['Image'].startswith('freeposte/'): | ||||
|             container = dockercli.inspect_container(brief['Id']) | ||||
|             container['Image'] = dockercli.inspect_image(container['Image']) | ||||
|             name = container['Config']['Labels']['com.docker.compose.service'] | ||||
|             containers[name] = container | ||||
|     return flask.render_template('services.html', containers=containers) | ||||
|   | ||||
							
								
								
									
										49
									
								
								admin/freeposte/admin/views/managers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								admin/freeposte/admin/views/managers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| from freeposte.admin import app, db, models, forms, utils | ||||
| from flask.ext import login as flask_login | ||||
|  | ||||
| import os | ||||
| import flask | ||||
| import wtforms_components | ||||
|  | ||||
|  | ||||
| @app.route('/manager/list/<domain_name>', methods=['GET']) | ||||
| @flask_login.login_required | ||||
| def manager_list(domain_name): | ||||
|     domain = utils.get_domain_admin(domain_name) | ||||
|     return flask.render_template('manager/list.html', domain=domain) | ||||
|  | ||||
|  | ||||
| @app.route('/manager/create/<domain_name>', methods=['GET', 'POST']) | ||||
| @flask_login.login_required | ||||
| def manager_create(domain_name): | ||||
|     domain = utils.get_domain_admin(domain_name) | ||||
|     form = forms.ManagerForm() | ||||
|     if form.validate_on_submit(): | ||||
|         user = utils.get_user(form.manager.data, admin=True) | ||||
|         if user in domain.managers: | ||||
|             flask.flash('User %s is already manager' % user, 'error') | ||||
|         else: | ||||
|             domain.managers.append(user) | ||||
|             db.session.add(domain) | ||||
|             db.session.commit() | ||||
|             flask.flash('User %s can now manage %s' % (user, domain.name)) | ||||
|             return flask.redirect( | ||||
|                 flask.url_for('.manager_list', domain_name=domain.name)) | ||||
|     return flask.render_template('manager/create.html', | ||||
|         domain=domain, form=form) | ||||
|  | ||||
|  | ||||
| @app.route('/manager/delete/<manager>', methods=['GET']) | ||||
| @flask_login.login_required | ||||
| def manager_delete(manager): | ||||
|     user = utils.get_user(manager, admin=True) | ||||
|     domain = utils.get_domain_admin(user.domain_name) | ||||
|     if user in domain.managers: | ||||
|         domain.managers.remove(user) | ||||
|         db.session.add(domain) | ||||
|         db.session.commit() | ||||
|         flask.flash('User %s can no longer manager %s' % (user, domain)) | ||||
|     else: | ||||
|         flask.flash('User %s is not manager' % user, 'error') | ||||
|     return flask.redirect( | ||||
|         flask.url_for('.manager_list', domain_name=domain.name)) | ||||
		Reference in New Issue
	
	Block a user