Passed
Push — master ( 54115d...67a0b3 )
by meta
02:30
created

AuthController::getMicrosoftGraphGroupMembership()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 1
dl 0
loc 20
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Metaclassing\EnterpriseAuth\Controller;
4
5
use Illuminate\Routing\Controller;
6
use Laravel\Socialite\Facades\Socialite;
7
8
class AuthController extends Controller
9
{
10
    protected $azureActiveDirectory;
11
12
    public function __construct()
13
    {
14
        $this->azureActiveDirectory = new \Metaclassing\EnterpriseAuth\AzureActiveDirectory(ENV('AZURE_AD_TENANT'));
15
    }
16
17
    // This is called after a web auth gets an access token, or api auth sends an access token
18
    public function validateOauthCreateOrUpdateUserAndGroups($accessToken)
19
    {
20
        $userData = $this->getMicrosoftGraphSelf($accessToken);
21
        $userData = $this->scrubMicrosoftGraphUserData($userData);
22
23
        // This is a laravel \App\User
24
        $user = $this->findOrCreateUser($userData);
25
26
        // Try to update the group/role membership for this user
27
        $this->updateGroups($user);
28
29
        // Cache the users oauth accss token mapped to their user object for stuff and things
30
        $key = '/oauth/tokens/'.$accessToken;
31
        // TODO: Replace static value 1440 with actual life of the oauth access token we got
32
        \Cache::put($key, $user, 1440);
33
34
        return $user;
35
    }
36
37
    public function getMicrosoftGraphSelf($accessToken)
38
    {
39
        $graph = new \Microsoft\Graph\Graph();
40
        $graph->setAccessToken($accessToken);
41
        $user = $graph->createRequest("GET", "/me")
42
                      ->setReturnType(\Microsoft\Graph\Model\User::class)
43
                      ->execute();
44
45
        return $user->jsonSerialize();
46
    }
47
48
    public function scrubMicrosoftGraphUserData($userData)
49
    {
50
        // Fix any stupid crap with missing or null fields
51
        if (! isset($userData['mail']) || !$userData['mail']) {
52
            $userData['mail'] = $userData['userPrincipalName'];
53
        }
54
55
        // Make sure we have email set for laravel \App\User
56
        $userData['email'] = $userData['mail'];
57
58
        // Password should be blank as a mater of principal
59
        $userData['password'] = '';
60
61
        return $userData;
62
    }
63
64
    protected function findOrCreateUser($userData)
65
    {
66
        // Configurable \App\User type and ID field name
67
        $userType = config('enterpriseauth.user_class');
68
        $userIdField = config('enterpriseauth.user_id_field');
69
        // Try to find an existing user
70
        $user = $userType::where($userIdField, $userData['id'])->first();
71
        // If we dont have an existing user
72
        if (! $user) {
73
            // Go create a new one with this data
74
            $UserFactory = new UserFactory();
0 ignored issues
show
Bug introduced by
The type Metaclassing\EnterpriseAuth\Controller\UserFactory was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
75
            $user = $UserFactory->convertAzureUser($userData);
76
        }
77
78
        return $user;
79
    }
80
81
    public function certAuth()
82
    {
83
        // Make sure we got a client certificate from the web server
84
        if (! $_SERVER['SSL_CLIENT_CERT']) {
85
            throw new \Exception('TLS client certificate missing');
86
        }
87
        // try to parse the certificate we got
88
        $x509 = new \phpseclib\File\X509();
89
        // NGINX screws up the cert by putting a bunch of tab characters into it so we need to clean those out
90
        $asciicert = str_replace("\t", '', $_SERVER['SSL_CLIENT_CERT']);
91
        $cert = $x509->loadX509($asciicert);
0 ignored issues
show
Unused Code introduced by
The assignment to $cert is dead and can be removed.
Loading history...
92
        $names = $x509->getExtension('id-ce-subjectAltName');
93
        if (! $names) {
94
            throw new \Exception('TLS client cert missing subject alternative names');
95
        }
96
        // Search subject alt names for user principal name
97
        $upn = '';
98
        foreach ($names as $name) {
99
            foreach ($name as $key => $value) {
100
                if ($key == 'otherName') {
101
                    if (isset($value['type-id']) && $value['type-id'] == '1.3.6.1.4.1.311.20.2.3') {
102
                        $upn = $value['value']['utf8String'];
103
                    }
104
                }
105
            }
106
        }
107
        if (! $upn) {
108
            throw new \Exception('Could not find user principal name in TLS client cert');
109
        }
110
        $user_class = config('enterpriseauth.user_class');
111
        $user = $user_class::where('userPrincipalName', $upn)->first();
112
        if (! $user) {
113
            throw new \Exception('No user found with user principal name '.$upn);
114
        }
115
        //dd($user);
116
        return $user;
117
    }
118
119
    public function updateGroups($user)
120
    {
121
        // See if we can get the users group membership data
122
        $groupData = $this->getMicrosoftGraphGroupMembership($user);
123
124
        // Process group data into a list of displayNames we use as roles
125
        $groups = [];
126
        foreach($groupData as $info) {
127
            $groups[] = $info['displayName'];
128
        }
129
130
        // If we have user group information from this oauth attempt
131
        if (count($groups)) {
132
            // remove the users existing database roles before assigning new ones
133
            \DB::table('assigned_roles')
134
               ->where('entity_id', $user->id)
135
               ->where('entity_type', get_class($user))
136
               ->delete();
137
            // add the user to each group they are assigned
138
            $user->assign($groups);
139
        }
140
    }
141
142
    public function getMicrosoftGraphGroupMembership($user)
143
    {
144
        // Get an access token for our application (not the users token)
145
        $accessToken = $this->azureActiveDirectory->getApplicationAccessToken(ENV('AZURE_AD_CLIENT_ID'), ENV('AZURE_AD_CLIENT_SECRET'));
146
147
        // Use the app access token to get a given users group membership
148
        $graph = new \Microsoft\Graph\Graph();
149
        $graph->setAccessToken($accessToken);
150
        $path = '/users/'.$user->userPrincipalName.'/memberOf';
151
        $groups = $graph->createRequest('GET', $path)
152
                        ->setReturnType(\Microsoft\Graph\Model\Group::class)
153
                        ->execute();
154
155
        // Convert the microsoft graph group objects into data that is useful
156
        $groupData = [];
157
        foreach ($groups as $group) {
158
            $groupData[] = $group->jsonSerialize();
159
        }
160
161
        return $groupData;
162
    }
163
164
}
165