• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grafana / django-saml2-auth / 9202044683

23 May 2024 04:05AM CUT coverage: 90.335%. Remained the same
9202044683

Pull #304

github

web-flow
Merge 42a19fe60 into bb893cb8d
Pull Request #304: Bump ruff from 0.4.1 to 0.4.5

916 of 1014 relevant lines covered (90.34%)

6.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.5
/django_saml2_auth/utils.py
1
"""Utility functions for dealing with various parts of the library.
7✔
2
E.g. creating SAML client, creating user, exception handling, etc.
3
"""
4

5
import base64
7✔
6
from functools import wraps
7✔
7
from importlib import import_module
7✔
8
import logging
7✔
9
from typing import Any, Callable, Dict, Iterable, Mapping, Optional, Tuple, Union
7✔
10

11
from dictor import dictor  # type: ignore
7✔
12
from django.conf import settings
7✔
13
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
7✔
14
from django.shortcuts import render
7✔
15
from django.urls import NoReverseMatch, reverse
7✔
16
from django.utils.module_loading import import_string
7✔
17
from django_saml2_auth.errors import (
7✔
18
    EMPTY_FUNCTION_PATH,
19
    GENERAL_EXCEPTION,
20
    IMPORT_ERROR,
21
    NO_REVERSE_MATCH,
22
    PATH_ERROR,
23
)
24
from django_saml2_auth.exceptions import SAMLAuthError
7✔
25

26

27
def run_hook(
7✔
28
    function_path: str, *args: Optional[Tuple[Any]], **kwargs: Optional[Mapping[str, Any]]
29
) -> Optional[Any]:
30
    """Runs a hook function with given args and kwargs. For example, given
31
    "models.User.create_new_user", the "create_new_user" function is imported from
32
    the "models.User" module and run with args and kwargs. Functions can be
33
    imported directly from modules, without having to be inside any class.
34

35
    Args:
36
        function_path (str): A path to a hook function,
37
            e.g. models.User.create_new_user (static method)
38

39
    Raises:
40
        SAMLAuthError: function_path isn't specified
41
        SAMLAuthError: There's nothing to import. Check your hook's import path!
42
        SAMLAuthError: Import error
43
        SAMLAuthError: Re-raise any exception caused by the called function
44

45
    Returns:
46
        Optional[Any]: Any result returned from running the hook function. None is returned in case
47
            of any exceptions, errors in arguments and related issues.
48
    """
49
    if not function_path:
7✔
50
        raise SAMLAuthError(
7✔
51
            "function_path isn't specified",
52
            extra={
53
                "exc_type": ValueError,
54
                "error_code": EMPTY_FUNCTION_PATH,
55
                "reason": "There was an error processing your request.",
56
                "status_code": 500,
57
            },
58
        )
59

60
    path = function_path.split(".")
7✔
61
    if len(path) < 2:
7✔
62
        # Nothing to import
63
        raise SAMLAuthError(
7✔
64
            "There's nothing to import. Check your hook's import path!",
65
            extra={
66
                "exc_type": ValueError,
67
                "error_code": PATH_ERROR,
68
                "reason": "There was an error processing your request.",
69
                "status_code": 500,
70
            },
71
        )
72

73
    module_path = ".".join(path[:-1])
7✔
74
    result = None
7✔
75
    try:
7✔
76
        cls = import_module(module_path)
7✔
77
    except ModuleNotFoundError:
×
78
        try:
×
79
            cls = import_string(module_path)
×
80
        except ImportError as exc:
×
81
            raise SAMLAuthError(
×
82
                str(exc),
83
                extra={
84
                    "exc": exc,
85
                    "exc_type": type(exc),
86
                    "error_code": IMPORT_ERROR,
87
                    "reason": "There was an error processing your request.",
88
                    "status_code": 500,
89
                },
90
            )
91
    try:
7✔
92
        result = getattr(cls, path[-1])(*args, **kwargs)
7✔
93
    except SAMLAuthError as exc:
7✔
94
        # Re-raise the exception
95
        raise exc
×
96
    except AttributeError as exc:
7✔
97
        raise SAMLAuthError(
7✔
98
            str(exc),
99
            extra={
100
                "exc": exc,
101
                "exc_type": type(exc),
102
                "error_code": IMPORT_ERROR,
103
                "reason": "There was an error processing your request.",
104
                "status_code": 500,
105
            },
106
        )
107
    except Exception as exc:
7✔
108
        raise SAMLAuthError(
7✔
109
            str(exc),
110
            extra={
111
                "exc": exc,
112
                "exc_type": type(exc),
113
                "error_code": GENERAL_EXCEPTION,
114
                "reason": "There was an error processing your request.",
115
                "status_code": 500,
116
            },
117
        )
118

119
    return result
7✔
120

121

122
def get_reverse(objects: Union[Any, Iterable[Any]]) -> Optional[str]:
7✔
123
    """Given one or a list of views/urls(s), returns the corresponding URL to that view.
124

125
    Args:
126
        objects (Union[Any, Iterable[Any]]): One or many views/urls representing a resource
127

128
    Raises:
129
        SAMLAuthError: We got a URL reverse issue: [...]
130

131
    Returns:
132
        Optional[str]: The URL to the resource or None.
133
    """
134
    if not isinstance(objects, (list, tuple)):
7✔
135
        objects = [objects]
7✔
136

137
    for obj in objects:
7✔
138
        try:
7✔
139
            return reverse(obj)
7✔
140
        except NoReverseMatch:
7✔
141
            pass
7✔
142
    raise SAMLAuthError(
7✔
143
        f"We got a URL reverse issue: {str(objects)}",
144
        extra={
145
            "exc_type": NoReverseMatch,
146
            "error_code": NO_REVERSE_MATCH,
147
            "reason": "There was an error processing your request.",
148
            "status_code": 500,
149
        },
150
    )
151

152

153
def exception_handler(
7✔
154
    function: Callable[..., Union[HttpResponse, HttpResponseRedirect]],
155
) -> Callable[..., Union[HttpResponse, HttpResponseRedirect]]:
156
    """This decorator can be used by view function to handle exceptions
157

158
    Args:
159
        function (Callable[..., Union[HttpResponse, HttpResponseRedirect]]):
160
            View function to decorate
161

162
    Returns:
163
        Callable[..., Union[HttpResponse, HttpResponseRedirect]]:
164
            Decorated view function with exception handling
165
    """
166

167
    def handle_exception(exc: Exception, request: HttpRequest) -> HttpResponse:
7✔
168
        """Render page with exception details
169

170
        Args:
171
            exc (Exception): An exception
172
            request (HttpRequest): Incoming http request object
173

174
        Returns:
175
            HttpResponse: Rendered error page with details
176
        """
177
        logger = logging.getLogger(__name__)
7✔
178
        if dictor(settings.SAML2_AUTH, "DEBUG", False):
7✔
179
            # Log the exception with traceback
180
            logger.exception(exc)
×
181
        else:
182
            # Log the exception without traceback
183
            logger.debug(exc)
7✔
184

185
        context: Optional[Dict[str, Any]] = exc.extra if isinstance(exc, SAMLAuthError) else {}
7✔
186
        if isinstance(exc, SAMLAuthError) and exc.extra:
7✔
187
            status = exc.extra.get("status_code")
7✔
188
        else:
189
            status = 500
×
190

191
        return render(request, "django_saml2_auth/error.html", context=context, status=status)
7✔
192

193
    @wraps(function)
7✔
194
    def wrapper(request: HttpRequest) -> HttpResponse:
7✔
195
        """Decorated function is wrapped and called here
196

197
        Args:
198
            request ([type]): [description]
199

200
        Returns:
201
            HttpResponse: Either a redirect or a response with error details
202
        """
203
        result = None
7✔
204
        try:
7✔
205
            result = function(request)
7✔
206
        except (SAMLAuthError, Exception) as exc:
7✔
207
            result = handle_exception(exc, request)
7✔
208
        return result
7✔
209

210
    return wrapper
7✔
211

212

213
def is_jwt_well_formed(jwt: str):
7✔
214
    """Check if JWT is well formed
215

216
    Args:
217
        jwt (str): Json Web Token
218

219
    Returns:
220
        Boolean: True if JWT is well formed, otherwise False
221
    """
222
    if isinstance(jwt, str):
7✔
223
        # JWT should contain three segments, separated by two period ('.') characters.
224
        jwt_segments = jwt.split(".")
7✔
225
        if len(jwt_segments) == 3:
7✔
226
            jose_header = jwt_segments[0]
7✔
227
            # base64-encoded string length should be a multiple of 4
228
            if len(jose_header) % 4 == 0:
7✔
229
                try:
7✔
230
                    jh_decoded = base64.b64decode(jose_header).decode("utf-8")
7✔
231
                    if jh_decoded and jh_decoded.find("JWT") > -1:
7✔
232
                        return True
7✔
233
                except Exception:
×
234
                    return False
×
235
    # If tests not passed return False
236
    return False
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc