Passed
Push — master ( ae195e...1cc4f4 )
by meta
02:53
created

AuthController   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 168
rs 10
c 0
b 0
f 0
wmc 25

9 Methods

Rating   Name   Duplication   Size   Complexity  
A updateGroups() 0 20 3
D certAuth() 0 36 10
A validateOauthCreateOrUpdateUserAndGroups() 0 17 1
A __construct() 0 3 1
A createUserFromAzureData() 0 18 2
A getMicrosoftGraphSelf() 0 9 1
A getMicrosoftGraphGroupMembership() 0 20 2
A findOrCreateUser() 0 14 2
A scrubMicrosoftGraphUserData() 0 8 3
1
<?php
2
3
namespace Metaclassing\EnterpriseAuth\Controllers;
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
        return $userData;
56
    }
57
58
    protected function findOrCreateUser($userData)
59
    {
60
        // Configurable \App\User type and ID field name
61
        $userType = config('enterpriseauth.user_class');
62
        $userIdField = config('enterpriseauth.user_id_field');
63
        // Try to find an existing user
64
        $user = $userType::where($userIdField, $userData['id'])->first();
65
        // If we dont have an existing user
66
        if (! $user) {
67
            // Go create a new one with this data
68
            $user = $this->createUserFromAzureData($userData);
69
        }
70
71
        return $user;
72
    }
73
74
    // This takes the azure userdata and makes a new user out of it
75
    public function createUserFromAzureData($userData)
76
    {
77
        // Config options for user type/id/field map
78
        $userType = config('enterpriseauth.user_class');
79
        $userFieldMap = config('enterpriseauth.user_map');
80
        $idField = config('enterpriseauth.user_id_field');
81
82
        // Should build new \App\User
83
        $user = new $userType();
84
        $user->$idField = $userData['id'];
85
        // Go through any other fields the config wants us to map
86
        foreach ($userFieldMap as $azureField => $userField) {
87
            $user->$userField = $userData[$azureField];
88
        }
89
        // Save our newly minted user
90
        $user->save();
91
92
        return $user;
93
    }
94
95
    public function certAuth()
96
    {
97
        // Make sure we got a client certificate from the web server
98
        if (! $_SERVER['SSL_CLIENT_CERT']) {
99
            throw new \Exception('TLS client certificate missing');
100
        }
101
        // try to parse the certificate we got
102
        $x509 = new \phpseclib\File\X509();
103
        // NGINX screws up the cert by putting a bunch of tab characters into it so we need to clean those out
104
        $asciicert = str_replace("\t", '', $_SERVER['SSL_CLIENT_CERT']);
105
        $cert = $x509->loadX509($asciicert);
0 ignored issues
show
Unused Code introduced by
The assignment to $cert is dead and can be removed.
Loading history...
106
        $names = $x509->getExtension('id-ce-subjectAltName');
107
        if (! $names) {
108
            throw new \Exception('TLS client cert missing subject alternative names');
109
        }
110
        // Search subject alt names for user principal name
111
        $upn = '';
112
        foreach ($names as $name) {
113
            foreach ($name as $key => $value) {
114
                if ($key == 'otherName') {
115
                    if (isset($value['type-id']) && $value['type-id'] == '1.3.6.1.4.1.311.20.2.3') {
116
                        $upn = $value['value']['utf8String'];
117
                    }
118
                }
119
            }
120
        }
121
        if (! $upn) {
122
            throw new \Exception('Could not find user principal name in TLS client cert');
123
        }
124
        $user_class = config('enterpriseauth.user_class');
125
        $user = $user_class::where('userPrincipalName', $upn)->first();
126
        if (! $user) {
127
            throw new \Exception('No user found with user principal name '.$upn);
128
        }
129
        //dd($user);
130
        return $user;
131
    }
132
133
    public function updateGroups($user)
134
    {
135
        // See if we can get the users group membership data
136
        $groupData = $this->getMicrosoftGraphGroupMembership($user);
137
138
        // Process group data into a list of displayNames we use as roles
139
        $groups = [];
140
        foreach ($groupData as $info) {
141
            $groups[] = $info['displayName'];
142
        }
143
144
        // If we have user group information from this oauth attempt
145
        if (count($groups)) {
146
            // remove the users existing database roles before assigning new ones
147
            \DB::table('assigned_roles')
148
               ->where('entity_id', $user->id)
149
               ->where('entity_type', get_class($user))
150
               ->delete();
151
            // add the user to each group they are assigned
152
            $user->assign($groups);
153
        }
154
    }
155
156
    public function getMicrosoftGraphGroupMembership($user)
157
    {
158
        // Get an access token for our application (not the users token)
159
        $accessToken = $this->azureActiveDirectory->getApplicationAccessToken(ENV('AZURE_AD_CLIENT_ID'), ENV('AZURE_AD_CLIENT_SECRET'));
160
161
        // Use the app access token to get a given users group membership
162
        $graph = new \Microsoft\Graph\Graph();
163
        $graph->setAccessToken($accessToken);
164
        $path = '/users/'.$user->userPrincipalName.'/memberOf';
165
        $groups = $graph->createRequest('GET', $path)
166
                        ->setReturnType(\Microsoft\Graph\Model\Group::class)
167
                        ->execute();
168
169
        // Convert the microsoft graph group objects into data that is useful
170
        $groupData = [];
171
        foreach ($groups as $group) {
172
            $groupData[] = $group->jsonSerialize();
173
        }
174
175
        return $groupData;
176
    }
177
}
178