1 import os
2 import base64
3 import datetime
4 import functools
5 from functools import wraps, partial
6
7 from netaddr import IPAddress, IPNetwork
8 import re
9 import flask
10 from flask import send_file
11
12 from urllib.parse import urlparse
13 from openid_teams.teams import TeamsRequest
14
15 from copr_common.enums import RoleEnum
16 from coprs import app
17 from coprs import db
18 from coprs import helpers
19 from coprs import models
20 from coprs import oid
21 from coprs.logic.complex_logic import ComplexLogic
22 from coprs.logic.users_logic import UsersLogic
23 from coprs.logic.coprs_logic import CoprsLogic
40
43 oidname_parse = urlparse(oidname)
44 if not oidname_parse.netloc:
45 return oidname
46 config_parse = urlparse(app.config["OPENID_PROVIDER_URL"])
47 return oidname_parse.netloc.replace(".{0}".format(config_parse.netloc), "")
48
62
67
80
81
82 -def page_not_found(message):
83 return flask.render_template("404.html", message=message), 404
84
88
91 """
92 :type message: str
93 :type err: CoprHttpException
94 """
95 return flask.render_template("_error.html",
96 message=message,
97 error_code=code,
98 error_title=title), code
99
100
101 server_error_handler = partial(generic_error, code=500, title="Internal Server Error")
102 bad_request_handler = partial(generic_error, code=400, title="Bad Request")
103 conflict_request_handler = partial(generic_error, code=409, title="Conflict")
104
105 misc = flask.Blueprint("misc", __name__)
106
107
108 @misc.route(app.config['KRB5_LOGIN_BASEURI'] + "<name>/", methods=["GET"])
110 """
111 Handle the Kerberos authentication.
112
113 Note that if we are able to get here, either the user is authenticated
114 correctly, or apache is mis-configured and it does not perform KRB
115 authentication at all. Note also, even if that can be considered ugly, we
116 are reusing oid's get_next_url feature with kerberos login.
117 """
118
119
120 if flask.g.user is not None:
121 return flask.redirect(oid.get_next_url())
122
123 krb_config = app.config['KRB5_LOGIN']
124
125 found = None
126 for key in krb_config.keys():
127 if krb_config[key]['URI'] == name:
128 found = key
129 break
130
131 if not found:
132
133 return flask.render_template("404.html"), 404
134
135 if app.config["DEBUG"] and 'TEST_REMOTE_USER' in os.environ:
136
137 flask.request.environ['REMOTE_USER'] = os.environ['TEST_REMOTE_USER']
138
139 if 'REMOTE_USER' not in flask.request.environ:
140 nocred = "Kerberos authentication failed (no credentials provided)"
141 return flask.render_template("403.html", message=nocred), 403
142
143 krb_username = flask.request.environ['REMOTE_USER']
144 app.logger.debug("krb5 login attempt: " + krb_username)
145 username = krb_straighten_username(krb_username)
146 if not username:
147 message = "invalid krb5 username: " + krb_username
148 return flask.render_template("403.html", message=message), 403
149
150 krb_login = (
151 models.Krb5Login.query
152 .filter(models.Krb5Login.config_name == key)
153 .filter(models.Krb5Login.primary == username)
154 .first()
155 )
156 if krb_login:
157 flask.g.user = krb_login.user
158 flask.session['krb5_login'] = krb_login.user.name
159 flask.flash(u"Welcome, {0}".format(flask.g.user.name), "success")
160 return flask.redirect(oid.get_next_url())
161
162
163 user = models.User.query.filter(models.User.username == username).first()
164 if not user:
165
166 email = username + "@" + krb_config[key]['email_domain']
167 user = create_user_wrapper(username, email)
168 db.session.add(user)
169
170 krb_login = models.Krb5Login(user=user, primary=username, config_name=key)
171 db.session.add(krb_login)
172 db.session.commit()
173
174 flask.flash(u"Welcome, {0}".format(user.name), "success")
175 flask.g.user = user
176 flask.session['krb5_login'] = user.name
177 return flask.redirect(oid.get_next_url())
178
179
180 @misc.route("/login/", methods=["GET"])
181 @oid.loginhandler
182 -def login():
183 if not app.config['FAS_LOGIN']:
184 if app.config['KRB5_LOGIN']:
185 return krb5_login_redirect(next=oid.get_next_url())
186 flask.flash("No auth method available", "error")
187 return flask.redirect(flask.url_for("coprs_ns.coprs_show"))
188
189 if flask.g.user is not None:
190 return flask.redirect(oid.get_next_url())
191 else:
192
193 team_req = TeamsRequest(["_FAS_ALL_GROUPS_"])
194 return oid.try_login(app.config["OPENID_PROVIDER_URL"],
195 ask_for=["email", "timezone"],
196 extensions=[team_req])
197
235
236
237 @misc.route("/logout/")
238 -def logout():
239 flask.session.pop("openid", None)
240 flask.session.pop("krb5_login", None)
241 flask.flash(u"You were signed out")
242 return flask.redirect(oid.get_next_url())
243
246 @functools.wraps(f)
247 def decorated_function(*args, **kwargs):
248 token = None
249 apt_login = None
250 if "Authorization" in flask.request.headers:
251 base64string = flask.request.headers["Authorization"]
252 base64string = base64string.split()[1].strip()
253 userstring = base64.b64decode(base64string)
254 (apt_login, token) = userstring.decode("utf-8").split(":")
255 token_auth = False
256 if token and apt_login:
257 user = UsersLogic.get_by_api_login(apt_login).first()
258 if (user and user.api_token == token and
259 user.api_token_expiration >= datetime.date.today()):
260
261 if user.proxy and "username" in flask.request.form:
262 user = UsersLogic.get(flask.request.form["username"]).first()
263
264 token_auth = True
265 flask.g.user = user
266 if not token_auth:
267 url = 'https://' + app.config["PUBLIC_COPR_HOSTNAME"]
268 url = helpers.fix_protocol_for_frontend(url)
269
270 output = {
271 "output": "notok",
272 "error": "Login invalid/expired. Please visit {0}/api to get or renew your API token.".format(url),
273 }
274 jsonout = flask.jsonify(output)
275 jsonout.status_code = 401
276 return jsonout
277 return f(*args, **kwargs)
278 return decorated_function
279
282 krbc = app.config['KRB5_LOGIN']
283 for key in krbc:
284
285 return flask.redirect(flask.url_for("misc.krb5_login",
286 name=krbc[key]['URI'],
287 next=next))
288 flask.flash("Unable to pick krb5 login page", "error")
289 return flask.redirect(flask.url_for("coprs_ns.coprs_show"))
290
293 def view_wrapper(f):
294 @functools.wraps(f)
295 def decorated_function(*args, **kwargs):
296 if flask.g.user is None:
297 return flask.redirect(flask.url_for("misc.login",
298 next=flask.request.url))
299
300 if role == RoleEnum("admin") and not flask.g.user.admin:
301 flask.flash("You are not allowed to access admin section.")
302 return flask.redirect(flask.url_for("coprs_ns.coprs_show"))
303
304 return f(*args, **kwargs)
305 return decorated_function
306
307
308
309
310
311 if callable(role):
312 return view_wrapper(role)
313 else:
314 return view_wrapper
315
319 @functools.wraps(f)
320 def decorated_function(*args, **kwargs):
321 auth = flask.request.authorization
322 if not auth or auth.password != app.config["BACKEND_PASSWORD"]:
323 return "You have to provide the correct password\n", 401
324
325 return f(*args, **kwargs)
326 return decorated_function
327
330 @functools.wraps(f)
331 def decorated_function(*args, **kwargs):
332 ip_addr = IPAddress(flask.request.remote_addr)
333 accept_ranges = set(app.config.get("INTRANET_IPS", []))
334 accept_ranges.add("127.0.0.1")
335 if not any(ip_addr in IPNetwork(addr_or_net) for addr_or_net in accept_ranges):
336 return ("Stats can be update only from intranet hosts, "
337 "not {}, check config\n".format(flask.request.remote_addr)), 403
338
339 return f(*args, **kwargs)
340 return decorated_function
341
354 return wrapper
355
367 return wrapper
368
371 if not build:
372 return send_file("static/status_images/unknown.png",
373 mimetype='image/png')
374
375 if build.state in ["importing", "pending", "starting", "running"]:
376
377
378 response = send_file("static/status_images/in_progress.png",
379 mimetype='image/png')
380 response.headers['Cache-Control'] = 'no-cache'
381 return response
382
383 if build.state in ["succeeded", "skipped"]:
384 return send_file("static/status_images/succeeded.png",
385 mimetype='image/png')
386
387 if build.state == "failed":
388 return send_file("static/status_images/failed.png",
389 mimetype='image/png')
390
391 return send_file("static/status_images/unknown.png",
392 mimetype='image/png')
393