public
Description: These are projects that I use as a portfolio.
Homepage: http://swsnider.com
Clone URL: git://github.com/swsnider/portfolio.git
Click here to lend your support to: portfolio and make a donation at www.pledgie.com !
portfolio / googleauth / googleauth.module
100644 284 lines (258 sloc) 10.676 kb
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
<?php
// $Id$
 
/**
 * @file
 * Implement a single sign on provider for Google Apps for Education
 */
 
/**
 * Implementation of hook_help().
 */
function googleauth_help($section) {
  switch ($section) {
    case 'admin/help#googleauth':
      $output = '<p>'.t('The googleauth module implements a provider for the Google Apps Single Sign On API. All users with the <em>use googleauth</em> permission will be able to authenticate with your Google Apps install').'</p>';
      return $output;
      break;
   }
}
 
/**
 * Implementation of hook_menu().
 */
function googleauth_menu($may_cache) {
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'title' => t('Provider'),
      'path' => 'googleauth/signin',
      'callback' => 'googleauth_sign_in',
      'access' => user_access('use googleauth'),
      'type' => MENU_CALLBACK
    );
$items[] = array(
      'title' => t('Google Sign Out'),
      'path' => 'googleauth/signout',
      'callback' => 'googleauth_sign_out',
      'access' => user_access('use googleauth'),
      'type' => MENU_CALLBACK
    );
    $items[] = array(
    'path' => 'admin/settings/googleauth',
    'title' => t('googleauth module settings'),
    'description' => t('googleauth configuration settings for file paths'),
    'callback' => 'drupal_get_form',
    'callback arguments' => 'googleauth_adminpage',
    'access' => user_access('access administration pages'),
    'type' => MENU_NORMAL_ITEM,
   );
  }
  return $items;
}
 
/**
 * Reads in an XML string that has been gzipped and then base64 encoded, and returns the plaintext.
 */
 
function decode_and_unzip($req){
  $req = base64_decode($req);
  $req1 = gzinflate($req);
  //Note: One of my servers doesn't have this function for some reason, so use an alternative
  if ($req1 === FALSE) {
    $req1 = gzuncompress($req);
  }
  return $req1;
}
 
 
/**
 * Gets only the parts of the SAMLRequest that we actually want.
 */
function get_the_interesting_parts($req){
  $parser = xml_parser_create();
  $result1 = xml_parse_into_struct($parser, $req, $result, $index);
  if ($result1 == 0){
    return FALSE;
  }
  $interesting['acs_url'] = $result[0]['attributes']['ASSERTIONCONSUMERSERVICEURL'];
  $interesting['issue_instant'] = $result[0]['attributes']['ISSUEINSTANT'];
  $interesting['provider_name'] = $result[0]['attributes']['PROVIDERNAME'];
  $interesting['request_ID'] = $result[0]['attributes']['ID'];
  return $interesting;
}
 
/**
 * Get the wierd time format that SAML requires
 */
 
function get_wierd_time($time_off){
  return gmdate('Y-m-d\TH:i:s\Z', time() + $time_off);
}
 
/**
 * Generate a SAML ID
 */
 
function get_random_id(){
  $random_pool = 'abcdefghijklmnopqrstuvwxyz';
  $the_id = '';
  
  for ($i = 0; $i < 40; $i++ ) {
    $the_id .= $random_pool[rand(0,strlen($random_pool)-1)];
  }
  
  return $the_id;
 
}
 
/**
 * Generate the response xml document, and sign it with the key
 */
 
function get_the_actual_response($i, $public, $private){
  $curr = <<<EOF
<samlp:Response ID="{{RESPONSE_ID}}" IssueInstant="{{ISSUE_INSTANT}}" Version="2.0" Destination="{{DESTINATION}}" InResponseTo="{{REQUEST_ID}}" xmlns="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"><Signature xmlns="http://www.w3.org/2000/09/xmldsig#"><SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" /><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#{{RSADSA}}-sha1" /><Reference URI=""><Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" /></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" /><DigestValue></DigestValue></Reference></SignedInfo><SignatureValue></SignatureValue><KeyInfo><KeyValue></KeyValue></KeyInfo></Signature><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status><Assertion ID="{{ASSERTION_ID}}" IssueInstant="{{ISSUE_INSTANT}}" Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion"><Issuer>{{ISSUER_DOMAIN}}</Issuer><Subject><NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress">{{USERNAME_STRING}}</NameID><SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"/></Subject><Conditions NotBefore="{{NOT_BEFORE}}" NotOnOrAfter="{{NOT_ON_OR_AFTER}}"></Conditions><AuthnStatement AuthnInstant="{{AUTHN_INSTANT}}"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement></Assertion></samlp:Response>
EOF;
  $curr = str_replace('{{USERNAME_STRING}}', $i['user_name'], $curr);
  $curr = str_replace('{{RESPONSE_ID}}', get_random_id(), $curr);
  $curr = str_replace('{{ISSUE_INSTANT}}', get_wierd_time(0), $curr);
  $curr = str_replace('{{AUTHN_INSTANT}}', get_wierd_time(0), $curr);
  $curr = str_replace('{{NOT_BEFORE}}', $i['not_before'], $curr);
  $curr = str_replace('{{NOT_ON_OR_AFTER}}', $i['not_on_or_after'], $curr);
  $curr = str_replace('{{ASSERTION_ID}}', get_random_id(), $curr);
  $curr = str_replace('{{RSADSA}}', strtolower($i['key_type']), $curr);
  $curr = str_replace('{{REQUEST_ID}}', $i['request_ID'], $curr);
  $curr = str_replace('{{DESTINATION}}', $i['acs_url'], $curr);
  $curr = str_replace('{{ISSUER_DOMAIN}}', $i['domain_name'], $curr);
  $temp = tempnam('/var/tmp', 'TO_SIGN_');
  if (!$h = fopen($temp, 'w')) {
    drupal_set_message('Cannot open temporary file!');
    return FALSE;
  }
  if (fwrite($h, $curr) === FALSE) {
    drupal_set_message('Cannot write to temporary file');
    return FALSE;
  }
  fclose($h);
  $temp_out = tempnam('/var/tmp', 'SIGNED_');
  exec('chmod a+r ' . $temp);
  exec('chmod a+r ' . $temp_out,$trash);
  $result = exec(variable_get('googleauth_path_to_xmlsec','/usr/bin/xmlsec1') . ' sign --privkey-pem ' . $private . ' --pubkey-der ' . $public . ' --output ' . $temp_out . ' ' . $temp, $result);
  unlink($temp);
  $actual_response = file_get_contents($temp_out);
  if (!$actual_response){
    drupal_set_message('Signing failed!');
    return FALSE;
  }
  unlink($temp_out);
  return $actual_response;
}
 
/**
 * Retrieves the username from drupal
 */
 
function get_drupal_username(){
  global $user;
  return $user->name;
}
 
/**
 * Menu callback which responds to the Google Auth attempt.
 */
function googleauth_sign_in() {
  $user_name = get_drupal_username();
  $SAMLRequest = $_GET['SAMLRequest']; // This is base64 encoded and gzipped.
  if (!$SAMLRequest){
    //NOTE: This happens if you haven't logged in before attempting to
    // authenticate to google. For some reason, the login page doesn't
    // preserve GET variables, and according to the comments I've read,
    // there doesn't seem to be a way around it without modifying Drupal
    // Core itself (messy!).
    drupal_set_message(t("SAMLRequest not present! Please try again!"), 'error');
    drupal_goto();
  }
  $SAMLRequest = decode_and_unzip($SAMLRequest);
  if ($SAMLRequest === FALSE){
    drupal_set_message(t("SAMLRequest could not be decoded."), 'error');
    drupal_goto();
    return;
  }
  $RelayState = $_GET['RelayState']; // This is the url to send them back towards.
  if ($RelayState === ""){
    drupal_set_message(t("No page to send you to!"), 'error');
    drupal_goto();
    return;
  }
  $interesting = get_the_interesting_parts($SAMLRequest);
  if ($interesting === FALSE){
    drupal_set_message(t("SAMLRequest could not be decoded: the interesting parts could not be extracted."), 'error');
    drupal_goto();
    return;
  }
  $public = variable_get('googleauth_public_key_path', FALSE);
  if ($public === FALSE){
    drupal_set_message(t("Unable to find a public key!"), 'error');
    drupal_goto();
    return;
  }
  $private = variable_get('googleauth_private_key_path', FALSE);
  if ($private === FALSE){
    drupal_set_message(t("Unable to find a private key!"), 'error');
    drupal_goto();
    return;
  }
  $interesting['user_name'] = $user_name;
  $interesting['key_type'] = variable_get('googleauth_key_type', 'dsa');
  $interesting['not_before'] = get_wierd_time(-300000);
  $interesting['not_on_or_after'] = get_wierd_time(600000);
  $output = get_the_actual_response($interesting, $public, $private);
  if ($output === FALSE){
    drupal_goto();
    return;
  }
  $true_output =<<<EOF
  <form name="acsForm" action="{{ACS_URL}}" method="post">
    <div style="display: none">
    <textarea style="display:none;" rows=10 cols=80 name="SAMLResponse">{{SAMLResponse}}</textarea>
    <textarea style="display:none;" rows=10 cols=80 name="RelayState">{{RelayState}}</textarea>
    </div>
    <input type="submit" value="Click here to authenticate"/>
    </form>
EOF;
  $true_output = str_replace('{{ACS_URL}}', $interesting['acs_url'], $true_output);
  $true_output = str_replace('{{SAMLResponse}}', $output, $true_output);
  $true_output = str_replace('{{RelayState}}', $RelayState, $true_output);
  return $true_output;
}
 
/**
 * Menu callback which responds to sign out requests.
 */
 
function googleauth_sign_out() {
  //Function does nothing right now (although it could i.e. log the signout)
  drupal_set_message(t("Signed out of Google Apps"));
  drupal_goto();
}
 
/**
 * Implementation of hook_perm().
 */
 
function googleauth_perm() {
return array('use googleauth');
}
 
/**
 * Provides a form to get settings from the users
 */
 
function googleauth_adminpage(){
  $form['googleauth_key_type'] = array(
    '#type' => 'select',
    '#title' => t('Public/Private Key type'),
    '#default_value' => variable_get('googleauth_key_type', 'dsa'),
    '#options' => array('dsa' => t('dsa'), 'rsa' => t('rsa')),
    '#description' => t("The key generation algorithm."),
    '#required' => TRUE
  );
  $form['googleauth_private_key_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Path to private key'),
    '#default_value' => variable_get('googleauth_private_key_path', ''),
    '#description' => t("The full, absolute path to the private key."),
    '#required' => TRUE
  );
  $form['googleauth_public_key_path'] = array(
    '#type' => 'textfield',
    '#title' => t('Path to public key'),
    '#default_value' => variable_get('googleauth_public_key_path', ''),
    '#description' => t("The full, absolute path to the public key."),
    '#required' => TRUE
  );
  $form['googleauth_path_to_xmlsec'] = array(
    '#type' => 'textfield',
    '#title' => t('Path to xmlsec'),
    '#default_value' => variable_get('googleauth_path_to_xmlsec', '/usr/bin/xmlsec1'),
    '#description' => t("The full, absolute path to xmlsec."),
    '#required' => TRUE
  );
  return system_settings_form($form);
}