Skip to content
This repository was archived by the owner on Dec 21, 2019. It is now read-only.

Commit 69be11a

Browse files
committed
Extracted CTFd code
1 parent 660fb8f commit 69be11a

File tree

7 files changed

+448
-1
lines changed

7 files changed

+448
-1
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
# CTFd-Containers
1+
# CTFd-Docker
22
Plugin to give CTFd the ability to manage Docker containers

__init__.py

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from flask import current_app as app, render_template, request, redirect, jsonify, url_for, Blueprint
2+
from CTFd.utils import admins_only, is_admin, cache
3+
from CTFd.models import db
4+
from .models import Containers
5+
6+
from . import utils
7+
8+
def load(app):
9+
app.db.create_all()
10+
admin_containers = Blueprint('admin_containers', __name__, template_folder='templates')
11+
12+
13+
@admin_containers.route('/admin/containers', methods=['GET'])
14+
@admins_only
15+
def list_container():
16+
containers = Containers.query.all()
17+
for c in containers:
18+
c.status = utils.container_status(c.name)
19+
c.ports = ', '.join(utils.container_ports(c.name, verbose=True))
20+
return render_template('containers.html', containers=containers)
21+
22+
23+
@admin_containers.route('/admin/containers/<int:container_id>/stop', methods=['POST'])
24+
@admins_only
25+
def stop_container(container_id):
26+
container = Containers.query.filter_by(id=container_id).first_or_404()
27+
if utils.container_stop(container.name):
28+
return '1'
29+
else:
30+
return '0'
31+
32+
33+
@admin_containers.route('/admin/containers/<int:container_id>/start', methods=['POST'])
34+
@admins_only
35+
def run_container(container_id):
36+
container = Containers.query.filter_by(id=container_id).first_or_404()
37+
if utils.container_status(container.name) == 'missing':
38+
if utils.run_image(container.name):
39+
return '1'
40+
else:
41+
return '0'
42+
else:
43+
if utils.container_start(container.name):
44+
return '1'
45+
else:
46+
return '0'
47+
48+
49+
@admin_containers.route('/admin/containers/<int:container_id>/delete', methods=['POST'])
50+
@admins_only
51+
def delete_container(container_id):
52+
container = Containers.query.filter_by(id=container_id).first_or_404()
53+
if utils.delete_image(container.name):
54+
db.session.delete(container)
55+
db.session.commit()
56+
db.session.close()
57+
return '1'
58+
59+
60+
@admin_containers.route('/admin/containers/new', methods=['POST'])
61+
@admins_only
62+
def new_container():
63+
name = request.form.get('name')
64+
if not set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'):
65+
return redirect(url_for('admin_containers.list_container'))
66+
buildfile = request.form.get('buildfile')
67+
files = request.files.getlist('files[]')
68+
utils.create_image(name=name, buildfile=buildfile, files=files)
69+
utils.run_image(name)
70+
return redirect(url_for('admin_containers.list_container'))
71+
72+
73+
@admin_containers.route('/admin/containers/import', methods=['POST'])
74+
@admins_only
75+
def import_container():
76+
name = request.form.get('name')
77+
if not set(name) <= set('abcdefghijklmnopqrstuvwxyz0123456789-_'):
78+
return redirect(url_for('admin_containers.list_container'))
79+
utils.import_image(name=name)
80+
return redirect(url_for('admin_containers.list_container'))
81+
82+
app.register_blueprint(admin_containers)

config.html

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{% extends "admin/base.html" %}
2+
3+
{% block content %}
4+
{% endblock %}
5+
6+
{% block scripts %}
7+
<script type="text/javascript">
8+
window.location = script_root + "/admin/containers"
9+
</script>
10+
{% endblock %}

models.py

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from CTFd.models import db
2+
3+
4+
class Containers(db.Model):
5+
id = db.Column(db.Integer, primary_key=True)
6+
name = db.Column(db.String(80))
7+
buildfile = db.Column(db.Text)
8+
9+
def __init__(self, name, buildfile):
10+
self.name = name
11+
self.buildfile = buildfile
12+
13+
def __repr__(self):
14+
return "<Container ID:(0) {1}>".format(self.id, self.name)

requirements.txt

Whitespace-only changes.

templates/containers.html

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
{% extends "admin/base.html" %}
2+
3+
{% block content %}
4+
<div class="modal fade" id="create-container-modal" tabindex="-1" role="dialog" aria-labelledby="container-modal-label">
5+
<div class="modal-dialog" role="document">
6+
<div class="modal-content">
7+
<div class="modal-header">
8+
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
9+
aria-hidden="true">&times;</span></button>
10+
<h4 class="modal-title" id="container-modal-label">Create Container</h4>
11+
</div>
12+
<form method="POST" action="{{ request.script_root }}/admin/containers/new" enctype="multipart/form-data">
13+
<div class="modal-body">
14+
<div class="form-group">
15+
<label for="name">Name</label>
16+
<input type="text" class="form-control" name="name" placeholder="Enter container name">
17+
</div>
18+
<div class="form-group">
19+
<label for="buildfile-editor" class="control-label">Build File</label>
20+
<textarea id="buildfile-editor" class="form-control" name="buildfile" rows="10" placeholder="Enter container build file"></textarea>
21+
</div>
22+
<div class="form-group">
23+
<label for="container-files">Associated Files
24+
<i class="fa fa-question-circle gray-text" data-toggle="tooltip" data-placement="right" title="These files are uploaded alongside your buildfile"></i>
25+
</label>
26+
<input type="file" name="files[]" id="container-files" multiple>
27+
<sub class="help-block">Attach multiple files using Control+Click or Cmd+Click.</sub>
28+
</div>
29+
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
30+
</div>
31+
<div class="modal-footer">
32+
<button type="submit" class="btn btn-primary">Create</button>
33+
</div>
34+
</form>
35+
</div>
36+
</div>
37+
</div>
38+
39+
<div class="modal fade" id="import-container-modal" tabindex="-1" role="dialog" aria-labelledby="container-modal-label">
40+
<div class="modal-dialog" role="document">
41+
<div class="modal-content">
42+
<div class="modal-header">
43+
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
44+
aria-hidden="true">&times;</span></button>
45+
<h4 class="modal-title" id="container-modal-label">Import Image</h4>
46+
</div>
47+
<form method="POST" action="{{ request.script_root }}/admin/containers/import" enctype="multipart/form-data">
48+
<div class="modal-body">
49+
<div class="form-group">
50+
<label for="name">Name</label>
51+
<input type="text" class="form-control" name="name" placeholder="Enter image name">
52+
</div>
53+
<input type="hidden" value="{{ nonce }}" name="nonce" id="nonce">
54+
</div>
55+
<div class="modal-footer">
56+
<button type="submit" class="btn btn-primary">Create</button>
57+
</div>
58+
</form>
59+
</div>
60+
</div>
61+
</div>
62+
63+
64+
<div id="confirm" class="modal fade" tabindex="-1">
65+
<div class="modal-dialog">
66+
<div class="modal-content">
67+
<div class="modal-header">
68+
<h2 class="text-center"><span id="confirm-container-title"></span> Container</h2>
69+
</div>
70+
<div class="modal-body" style="height:110px">
71+
<div class="row-fluid">
72+
<div class="col-md-12">
73+
<form method="POST">
74+
<input id="nonce" type="hidden" name="nonce" value="{{ nonce }}">
75+
<div class="small-6 small-centered text-center columns">
76+
<p>Are you sure you want to <span id="confirm-container-method"></span> <strong id="confirm-container-name"></strong>?</p>
77+
<button type="button" data-dismiss="modal" class="btn btn-theme btn-outlined">No</button>
78+
<button type="button" id="confirm-container" class="btn btn-theme btn-outlined">Yes</button>
79+
</div>
80+
</form>
81+
</div>
82+
</div>
83+
</div>
84+
</div>
85+
</div>
86+
</div>
87+
88+
89+
<div class="row">
90+
<br>
91+
<div style="text-align:center">
92+
<h1>Containers</h1>
93+
<button class="btn btn-theme btn-outlined create-challenge" data-toggle="modal" data-target="#create-container-modal">
94+
New Container
95+
</button>
96+
<button class="btn btn-theme btn-outlined create-challenge" data-toggle="modal" data-target="#import-container-modal">
97+
Import Image
98+
</button>
99+
</div>
100+
<br>
101+
{% if containers %}
102+
<table id="teamsboard">
103+
<thead>
104+
<tr>
105+
<td class="text-center"><strong>Status</strong>
106+
</td>
107+
<td class="text-center"><strong>Name</strong>
108+
</td>
109+
<td class="text-center"><strong>Ports</strong>
110+
</td>
111+
<td class="text-center"><strong>Settings</strong>
112+
</td>
113+
</tr>
114+
</thead>
115+
<tbody>
116+
{% for c in containers %}
117+
<tr>
118+
<td class="text-center">{{ c.status }}</td>
119+
<td class="text-center container_item" id="{{ c.id }}">{{ c.name }}</td>
120+
<td class="text-center">{{ c.ports }}</td>
121+
<td class="text-center">
122+
<span>
123+
{% if c.status != 'running' %}
124+
<i class="fa fa-play"></i>
125+
{% else %}
126+
<i class="fa fa-stop"></i>
127+
{% endif %}
128+
<i class="fa fa-times"></i>
129+
</span>
130+
</td>
131+
</tr>
132+
{% endfor %}
133+
</tbody>
134+
</table>
135+
{% endif %}
136+
</div>
137+
{% endblock %}
138+
139+
{% block scripts %}
140+
<script>
141+
142+
function load_confirm_modal(title, url, container_name){
143+
var modal = $('#confirm')
144+
modal.find('#confirm-container-name').text(container_name)
145+
modal.find('#confirm-container-title').text(title)
146+
modal.find('#confirm-container-method').text(title.toLowerCase())
147+
$('#confirm form').attr('action', url);
148+
$('#confirm').modal('show');
149+
}
150+
151+
$('#confirm-container').click(function(e){
152+
e.preventDefault();
153+
var id = $('#confirm input[name="id"]').val()
154+
var user_data = $('#confirm form').serializeArray()
155+
$.post($('#confirm form').attr('action'), $('#confirm form').serialize(), function(data){
156+
var data = $.parseJSON(JSON.stringify(data))
157+
if (data == "1"){
158+
location.reload()
159+
}
160+
})
161+
});
162+
163+
164+
$('.fa-times').click(function(){
165+
var elem = $(this).parent().parent().parent().find('.container_item');
166+
var container = elem.attr('id');
167+
var container_name = elem.text().trim();
168+
load_confirm_modal('Delete', '/admin/containers/'+container+'/delete', container_name)
169+
});
170+
171+
$('.fa-play').click(function(){
172+
var elem = $(this).parent().parent().parent().find('.container_item');
173+
var container = elem.attr('id');
174+
var container_name = elem.text().trim();
175+
load_confirm_modal('Start', '/admin/containers/'+container+'/start', container_name)
176+
});
177+
178+
$('.fa-stop').click(function(){
179+
var elem = $(this).parent().parent().parent().find('.container_item');
180+
var container = elem.attr('id');
181+
var container_name = elem.text().trim();
182+
load_confirm_modal('Stop', '/admin/containers/'+container+'/stop', container_name)
183+
});
184+
185+
$(document).ready(function(){
186+
$('[data-toggle="tooltip"]').tooltip();
187+
});
188+
189+
</script>
190+
{% endblock %}

0 commit comments

Comments
 (0)