1 # ===================================================================== 2 # openidCheck: W-TW OpenID 1.1 dumb-mode 'check_authentication' method. 3 # 4 # Copyright (c) 2009 Carlo Strozzi 5 # 6 # This program is free software; you can redistribute it and/or modify 7 # it under the terms of the GNU General Public License as published by 8 # the Free Software Foundation; version 2 dated June, 1991. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program; if not, write to the Free Software 17 # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 18 # 19 # ===================================================================== 20 21 # ===================================================================== 22 # Local variables and functions 23 # ===================================================================== 24 25 TNS_GROUP_HOME = () 26 27 HTTP_AUTHORIZATION = () # *Always* clear any old auth. 28 29 cgi.group = () 30 cgi.group.literal = () 31 cgi.nonce = () 32 cgi.grep.user = () 33 cgi.return_names = () 34 cgi.return_values = () 35 cgi.regform_url = () 36 37 # Clear most common arguments that the OpenID server may/will return. 38 cgi.openid.assoc_handle = () 39 cgi.openid.identity = () 40 cgi.openid.mode = () 41 cgi.openid.op_endpoint = () 42 cgi.openid.response_nonce = () 43 cgi.openid.return_to = () 44 cgi.openid.sig = () 45 cgi.openid.signed = () 46 47 openid_url = () 48 openid_user = () 49 50 cursor = () 51 auth_failed = () 52 53 # ===================================================================== 54 # Main program 55 # ===================================================================== 56 57 # Although OpenID specs mandate GET here, the request is non-idempotent 58 # in that if the authentication request has already been positively 59 # 'consumed', any reload of this same URL will cause an is_valid:false 60 # reply from the OpenID provider, which is appropriate. 61 62 csaGetArgs GET 63 64 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 65 66 . $CSA_ROOT/lib/group-stuff.rc 67 68 # Check other required arguments. Note that if anything fails 69 # I simply consider it an authentication failure regardless of 70 # what actually happened, as I do not have much control on what 71 # an Identity Provider may return if anythinbg goes wrong. 72 73 ~ $'cgi.nonce' () && auth_failed = 1 74 ~ $'cgi.return_names' () && auth_failed = 1 75 ~ $'cgi.return_values' () && auth_failed = 1 76 ~ $'cgi.openid.op_endpoint' http://* https://* || auth_failed = 1 77 78 ~ $'cgi.openid.mode' id_res || { 79 csaSession.set - 15 # clear the supplied OpenID URL 80 auth_failed = 1 81 } 82 83 # The authentication failure page must be sent now that 84 # the group metadata has been loaded, not earlier. 85 86 ~ $auth_failed () || csaExit.needauth 87 88 # New authentication not allowed if already logged-in. 89 csaTrue $CSA_AUTH_OK && csaExit.fault 1029 90 91 # Grant further privileges if appropriate. 92 . $CSA_ROOT/lib/group-editor.rc 93 94 # Create the OpenID table if it does not yet exist; note the lock 95 # on the Principal Lock Semaphore (PLS) as opposed than on the actual 96 # OpenID table. 97 98 csaLock $TNS_USER_TABLE || csaExit.fault 99 100 if (!csaIsFullPath --exists --quiet $CSA_ROOT/var/pages/openid+dat) { 101 maketable --input \ 102 $CSA_ROOT/lib/openid.xrf > $CSA_ROOT/var/pages/openid+dat || 103 csaExit.fault 0003 maketable 104 } 105 106 # Check for MITM reply attacks. 107 ~ $'cgi.nonce' $CSA_SESSION(16) || csaExit.needauth 108 109 csaSession.set - 16 # housekeeping. 110 111 # Release the PLS ASAP, as curl(1) is yet to be run and it may take long. 112 113 csaUnlock $TNS_USER_TABLE 114 115 # Build URL for 'check_authentication'. 116 117 if (~ $'cgi.openid.op_endpoint' *'?'*) { 118 openid_url = $'cgi.openid.op_endpoint'^'&openid.mode=check_authentication' 119 } else { 120 openid_url = $'cgi.openid.op_endpoint'^'?openid.mode=check_authentication' 121 } 122 123 # Always append openid.signed, openid.assoc_handle and openid.sig . 124 openid_url = $openid_url'&openid.signed='$'cgi.openid.signed' 125 openid_url = $openid_url'&openid.assoc_handle='$'cgi.openid.assoc_handle' 126 openid_url = $openid_url'&openid.sig='$'cgi.openid.sig' 127 128 # Append any other signed fields last. 129 * = $'cgi.return_names' 130 131 while (!~ $1 ()) { 132 cursor = ($cursor 1) 133 openid_url = $openid_url'&openid.'$1'='$'cgi.return_values'($#cursor) 134 shift 135 } 136 137 # Call the OpenID provider asking for 'check_authentication'. 138 # I only take the first 50 KBytes of data of server response, 139 # to prevent trivial DoS attacks. I could read muck less than that, 140 # but if the server runs CSA it may prepend the response with the 141 # largish CSA RDF block, so I need to read a bit more. 142 143 # Better use HTTP/1.0 or curl(1) may hang this script by keeping the 144 # connection open, depending on how the remote site works. 145 curl -0 --silent --include --retry 1 --connect-timeout 10 \ 146 --ignore-content-length --range 0-50000 \ 147 --output $tmp1 $openid_url || csaExit.fault 0054 148 149 # Alternative way, using wget(1). Unfortunately if wget(1) is writing 150 # to a pipe and the latter is closed prematurely, as in this case, it 151 # returns "1" as opposed to SIGPIPE, making it impossible for us to 152 # understand whether the non-zero code was due to an unreachable URL or 153 # to the SIGPIPE, hence the need to act in two steps, as shown. 154 # 155 #wget -q -t 1 -T 10 --ignore-length \ 156 # -O - $openid_url | head -c 50000 > $tmp1 157 # 158 #test -s $tmp1 || csaExit.fault 0054 159 160 grep -qe is_valid:true $tmp1 || { 161 csaSession.set - 15 # clear the supplied OpenID URL 162 csaExit.needauth 163 } 164 165 # Check whether the specified user (if any) already has an 166 # OpenID-enabled account with us. 167 168 if (!~ $'cgi.grep.user' ()) { 169 openid_user = `{grep -e $tab$'cgi.grep.user'^'$' \ 170 $CSA_ROOT/var/pages/openid+dat} 171 } 172 173 # Turn next line into !~ for testing with an already known OpenID account. 174 if (~ $openid_user(2) ()) { 175 176 #TNS_SELFREG_AUTH=guest; ~ $REMOTE_ADDR 192.168.1.2 && TNS_SELFREG_AUTOASK=true 177 178 if (~ $TNS_SELFREG_AUTH *[a-z]* && csaTrue $TNS_SELFREG_AUTOASK) { 179 180 # Users that have successfully proved their identidy with OpenID 181 # but who are unknown to us are sent to the registration form, if 182 # self-registration is allowed. 183 184 # The call to 'showStatic' for this particular action will be non- 185 # RESTful, due to the need to possibly pass several arguments on 186 # the URL, which would make a RESTful rewrite quite difficult. 187 188 csaExit.location $CSA_RPC_URI'?0=showStatic&x-csa-lang='$CSA_LANG^'&1='$'cgi.group'^'&2=tw-reg-form&'$'cgi.regform_url' 189 190 } else { 191 csaSession.set - 15 # clear the supplied OpenID URL 192 csaExit.needauth 193 } 194 } 195 196 # The client is known to TW and she has succesfully authenticated 197 # through OpenID. 198 # The next steps are quite similar to those performed by 'logIn'. 199 200 ~ $#TNS_GROUP_HOME 1 2 || csaExit.fault 0041 TNS_GROUP_HOME # required 201 202 # Abide by the TW specs. 203 ~ $TNS_GROUP_HOME(2) () && 204 TNS_GROUP_HOME = ($TNS_GROUP_HOME(1) $TNS_GROUP_HOME(1)) 205 206 # The authCheck function is application-specific, and must be provided 207 # by the application programmer. We need to pass it the authentication 208 # data posted by the user, which was not known when 'authCheck' was 209 # called by 'weblib.rc', so we must call it again after 'csaGetArgs'. 210 211 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 212 213 authCheck --open-id --return || csaExit.needauth 214 215 # Set new auth, after passing the authorization check. 216 HTTP_AUTHORIZATION='Basic '^`{ 217 echo -n $CSA_AUTH_USER:$CSA_AUTH_PW | swu-encdec -e -b 218 } 219 220 csaPrintMsg 0014 # Log the event. 221 222 #~ $REMOTE_ADDR 192.168.1.2 && csaExit.env 223 224 # Take the user to the last failed authentication if applicable. 225 if (~ $CSA_SESSION(18) http* /*) { 226 csaSession.set - 18 227 if (csaIsInteractive) { 228 if (~ $CSA_SESSION(18) /$CSA_ID/*) { 229 csaExit.location $CSA_CGI_STEM$CSA_SESSION(18) 230 } else csaExit.location $CSA_SESSION(18) 231 } 232 } 233 234 # Else take the user to the welcome page. 235 csaExit.location \ 236 $CSA_RPC_URI/$CSA_LANG/$'cgi.group'/$TNS_GROUP_HOME(2) 237 238 #EOF