summaryrefslogtreecommitdiffstats
path: root/api/controllers/api/v1/auth.py
blob: 10261675487a4274f4691963213fc168b3a547f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
from flask import Blueprint, request, jsonify, abort, render_template
from models import db, OAuth2Client, User
from werkzeug.security import gen_salt
from app_oauth import authorization
from werkzeug.datastructures import ImmutableMultiDict

bp_api_v1_auth = Blueprint("bp_api_v1_auth", __name__)


@bp_api_v1_auth.route("/api/v1/apps", methods=["POST"])
def create_client():
    """
    Create a new application to obtain OAuth2 credentials.
    ---
    tags:
        - Apps
    responses:
        200:
            description: Returns App with client_id and client_secret
    """
    if request.form:
        req = request.form
    elif request.json:
        req = request.json
    else:
        abort(400)

    err = False
    if "client_name" not in req:
        err = True
    elif "redirect_uris" not in req:
        err = True
    elif "scopes" not in req:
        err = True

    if err:
        resp = {"error": "Required fields: client_name, redirect_uris, scopes"}
        response = jsonify(resp)
        response.mimetype = "application/json; charset=utf-8"
        response.status_code = 400
        return response

    client = OAuth2Client()
    client.client_id = gen_salt(24)
    metadatas = {
        "client_name": req.get("client_name"),
        "client_uri": req.get("website", None),
        "redirect_uris": [req.get("redirect_uris")],  # TODO, should make a check or something
        "scope": req.get("scopes"),
        # this needs to be hardcoded for whatever reason
        "response_type": "code",
        "grant_types": ["authorization_code", "client_credentials", "password"],
        "token_endpoint_auth_method": "client_secret_post",
    }
    if client.token_endpoint_auth_method == "none":
        client.client_secret = ""
    else:
        client.client_secret = gen_salt(48)

    client.set_client_metadata(metadatas)

    db.session.add(client)
    db.session.commit()

    resp = {
        "client_id": client.client_id,
        "client_secret": client.client_secret,
        "id": client.id,
        "name": client.client_name,
        "redirect_uris": client.redirect_uris[0] if len(client.redirect_uris) else "",
        "website": client.client_uri,
        "vapid_key": None,  # FIXME to implement this
    }
    response = jsonify(resp)
    response.mimetype = "application/json; charset=utf-8"
    response.status_code = 200
    return response


# This endpoint is used by the grants: authorization_code and implicit
@bp_api_v1_auth.route("/oauth/authorize", methods=["GET", "POST"])
def oauth_authorize():
    """
    Redirect here with response_type=code, client_id, client_secret, redirect_uri, scope.
    Displays an authorization form to the user.
    If approved, it will create and return an authorization code, then redirect to the desired redirect_uri, or show the authorization code if urn:ietf:wg:oauth:2.0:oob was requested.
    ---
    tags:
        - Authentication
    responses:
        200:
            description: ???
    """
    # input: client_id, client_secret, redirect_uri, scope
    # should authorize the user, and then return auth code if urn:ietf:wg:oauth:2.0:oob or redirect
    if request.method == "GET":
        scopes = request.args.get("scope", "").split(" ")
        return render_template("oauth/authorize.jinja2", scopes=scopes)
    else:
        grant_user = None

        if "username" and "password" in request.form:
            username = request.form.get("username")
            user = User.query.filter_by(name=username).first()
            if user and user.check_password(request.form.get("password")):
                grant_user = user

        return authorization.create_authorization_response(grant_user=grant_user)


@bp_api_v1_auth.route("/oauth/token", methods=["POST"])
def oauth_token():
    """
    Post here with authorization_code for authorization code grant type or username and password for password grant type.
    Returns an access token.
    This corresponds to the token endpoint, section 3.2 of the OAuth 2 RFC.
    ---
    tags:
        - Authentication
    responses:
        200:
            description: ???
    """
    if request.json:
        # Ugly workaround because authlib doesn't handle JSON queries
        d = {
            "client_id": request.json["client_id"],
            "client_secret": request.json["client_secret"],
            "grant_type": request.json["grant_type"],
            # This is an admin-fe workaround because scopes aren't specified
            "scope": request.json.get("scope", "read write follow"),
        }
        if request.json.get("password"):
            d["password"] = request.json["password"]
        if request.json.get("username"):
            d["username"] = request.json["username"]
        if request.json.get("code"):
            d["code"] = request.json["code"]
        if request.json.get("redirect_uri"):
            d["redirect_uri"] = request.json["redirect_uri"]

        request.form = ImmutableMultiDict(d)
    return authorization.create_token_response(request)


@bp_api_v1_auth.route("/oauth/revoke", methods=["POST"])
def oauth_revoke():
    """
    Post here with client credentials (in basic auth or in params client_id and client_secret) to revoke an access token.
    This corresponds to the token endpoint, using the OAuth 2.0 Token Revocation RFC (RFC 7009).
    ---
    tags:
        - Authentication
    responses:
        200:
            description: ???
    """
    # This endpoint wants basic auth, and it doesn't even works
    return authorization.create_endpoint_response("revocation")