diff --git a/actions/auth0.js b/actions/auth0.js new file mode 100644 index 000000000..a3aec90d5 --- /dev/null +++ b/actions/auth0.js @@ -0,0 +1,365 @@ +/* + * Copyright (C) 2014 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSASSEMBLER + */ +"use strict"; + +var async = require('async'); +var _ = require('underscore'); +var request = require('request'); + +var IllegalArgumentError = require('../errors/IllegalArgumentError'); +var NotFoundError = require('../errors/NotFoundError'); + +/** + * The login page. + */ +var LOGIN = "login"; + +/** + * The register page. + */ +var REGISTER = "register"; + +/** + * Set the name by provider id. + * + * @param resp - the json String + * @param socialAccount - the socialAccount instance + * @param helper - the helper instance + * @returns socialAccount - the socialAccount instance + */ +function setName(resp, socialAccount, helper) { + var providerId = socialAccount.providerId; + + if (providerId === helper.socialProviders.twitter) { + socialAccount.name = resp.screen_name; + } else if (providerId === helper.socialProviders.github) { + socialAccount.name = resp.nickname; + } else if (providerId === helper.socialProviders.ad) { + socialAccount.name = resp.nickname; + socialAccount.enterpriseLogin = true; + } else { + socialAccount.name = ''; + } + + return socialAccount; +} + +/** + * Parse the response json String to entity. + * + * @param resp - the response json String. + * @param helper - the helper instance + * @returns socialAccount - the parsed socialAccount instance. + */ +function parseUserInfo(resp, helper) { + //set the default value + var socialAccount = { + "name": '', + "email": '', + "givenName": '', + "familyName": '', + "emailVerified": false, + "providerId": 0, + "enterpriseLogin": false, + "socialUserId": '', + "jsonWebToken": '', + "accessToken": '' + }; + + if (_.isDefined(resp.identities) && resp.identities.length > 0) { + socialAccount.socialUserId = resp.identities[0].user_id; + } + + helper.getProviderId(resp.user_id, function (tmp, providerId) { + socialAccount.providerId = providerId; + }); + socialAccount = setName(resp, socialAccount, helper); + + if (_.isDefined(resp.email)) { + socialAccount.email = resp.email; + } + + if (_.isDefined(resp.email_verified)) { + socialAccount.emailVerified = resp.email_verified; + } + + if (_.isDefined(resp.family_name)) { + socialAccount.familyName = resp.family_name; + } + + if (_.isDefined(resp.given_name)) { + socialAccount.givenName = resp.given_name; + } + + return socialAccount; +} + +/** + * The method that exposes the auth0 Callback API. + */ +exports.action = { + name: 'auth0Callback', + description: 'The Auth0 Callback Method', + inputs : { + required : ['code'], + optional : ['state'] + }, + blockedConnectionTypes: [], + outputExample: {}, + version: 'v2', + transaction: 'write', + databases: ["common_oltp"], + run: function (api, connection, next) { + var helper = api.helper, code = connection.params.code, state = connection.params.state, + dbConnectionMap = connection.dbConnectionMap, sqlParams = {}, accessToken, idToken, responseResult = {}, + socialAccount, foundUserId = '', nextPage = ''; + + api.log("Executing auth0Callback#run", 'debug'); + api.log("code: " + code, 'debug'); + api.log("state: " + state, 'debug'); + + if (!connection.dbConnectionMap) { + api.helper.handleNoConnection(api, connection, next); + return; + } + + async.waterfall([ + function (cb) { + if (_.isDefined(state)) { + var urls = state.split("?"), np, params; + if (urls.length > 1) { + np = decodeURI(urls[0]); + params = urls[1].split("&"); + if (params.length > 1) { + nextPage = np; + } else { + nextPage = decodeURI(state); + } + } else { + nextPage = decodeURI(state); + } + responseResult.state = state; + } + responseResult.code = code; + + cb(); + }, function (cb) { + //get access token from auth0 + request.post("https://" + api.config.general.oauthDomain + ".auth0.com/oauth/token", function (err, res, body) { + api.log("response body: " + body, 'debug'); + api.log("err: " + err, 'debug'); + if (err || (res.statusCode !== 200 && res.statusCode !== 201)) { + cb(new IllegalArgumentError("Fails to get access token from auth0.")); + return; + } + var resp = JSON.parse(body); + accessToken = resp.access_token; + idToken = resp.id_token; + cb(); + }).form({ + "client_id" : api.config.general.oauthClientId, + "client_secret" : api.config.auth0.clientSecret, + "redirect_uri": api.config.auth0.serverName + api.config.auth0.redirectUrl, + "grant_type": "authorization_code", + "code" : code, + "scope" : "openid" + }); + }, function (cb) { + //get user info from auth0 + request("https://" + api.config.general.oauthDomain + ".auth0.com/userinfo?access_token=" + accessToken, + function (err, res, body) { + api.log("response body: " + body, 'debug'); + if (err || (res.statusCode !== 200 && res.statusCode !== 201)) { + cb(new IllegalArgumentError("Fails to get user info from auth0.")); + return; + } + + var resp = JSON.parse(body); + socialAccount = parseUserInfo(resp, helper); + socialAccount.accessToken = accessToken; + socialAccount.jsonWebToken = idToken; + + cb(); + }); + }, function (cb) { + if (socialAccount.enterpriseLogin === true) { + api.log("ldap login - get user", "debug"); + sqlParams.handle = socialAccount.name; + api.dataAccess.executeQuery('get_user_by_handle', sqlParams, dbConnectionMap, cb); + } else { + cb(null, null); + } + }, function (data, cb) { + if (socialAccount.enterpriseLogin === true) { + api.log("ldap login - get password", "debug"); + if (data.length !== 0) { + responseResult.handle = data[0].handle; + responseResult.userId = data[0].id; + sqlParams.userId = data[0].id; + } else { + responseResult.handle = ''; + responseResult.userId = 0; + sqlParams.userId = 0; + } + + api.dataAccess.executeQuery('get_password_by_user_id', sqlParams, dbConnectionMap, cb); + } else { + cb(null, null); + } + }, function (data, cb) { + if (socialAccount.enterpriseLogin === true) { + api.log("ldap login - forward", "debug"); + if (data.length !== 0) { + responseResult.password = data[0].password; + } else { + responseResult.password = ''; + } + + responseResult.result = LOGIN; + responseResult.nextPage = nextPage; + responseResult.socialAccount = socialAccount; + connection.response = responseResult; + next(connection, true); + return; + } + + if (socialAccount.socialUserId !== '') { + api.log("social login", "debug"); + sqlParams.socialUserId = socialAccount.socialUserId; + sqlParams.providerId = socialAccount.providerId; + api.dataAccess.executeQuery('get_user_id_by_social_user_id_and_provider_id', sqlParams, + dbConnectionMap, cb); + } else { + cb(null, null); + } + }, + function (data, cb) { + if (data !== null && data.length > 0) { + foundUserId = data[0].user_id; + } + api.log("foundUserId:" + foundUserId, "debug"); + + if (foundUserId === null || foundUserId === '') { + if (socialAccount.email === '' && socialAccount.name === '') { + cb(new NotFoundError("The social account should have at least one valid email or one valid username.")); + return; + } + + async.parallel({ + getUserIdByEmail: function (cbx) { + if (socialAccount.email !== '') { + sqlParams.email = socialAccount.email; + if (socialAccount.emailVerified === true) { + sqlParams.emailVerified = 't'; + } else { + sqlParams.emailVerified = 'f'; + } + sqlParams.providerId = socialAccount.providerId; + api.dataAccess.executeQuery('get_user_id_by_social_account_email', sqlParams, + dbConnectionMap, cbx); + } else { + cbx(); + } + }, + getUserIdByName: function (cbx) { + if (socialAccount.name !== '') { + sqlParams.userName = socialAccount.name; + sqlParams.providerId = socialAccount.providerId; + api.dataAccess.executeQuery('get_user_id_by_social_account_name', sqlParams, + dbConnectionMap, cbx); + } else { + cbx(); + } + } + }, cb); + } else { + cb(null, null); + } + }, function (data, cb) { + api.log("foundUserId:" + foundUserId, "debug"); + if (foundUserId === null || foundUserId === '') { + if (data !== null && data.getUserIdByEmail !== null && data.getUserIdByEmail !== undefined + && data.getUserIdByEmail.length > 0) { + foundUserId = data.getUserIdByEmail[0].user_id; + } else if (data !== null && data.getUserIdByName !== null && data.getUserIdByName !== undefined + && data.getUserIdByName.length > 0) { + foundUserId = data.getUserIdByName[0].user_id; + } + + if (foundUserId !== null && foundUserId !== '') { + sqlParams.socialUserId = socialAccount.socialUserId; + sqlParams.userId = foundUserId; + api.dataAccess.executeQuery('update_social_user_id', sqlParams, dbConnectionMap, cb); + } else { + cb(null); + } + } else { + cb(null); + } + }, function (cb) { + if (foundUserId !== null && foundUserId !== '') { + sqlParams.userId = foundUserId; + api.dataAccess.executeQuery('get_user_by_user_id', sqlParams, dbConnectionMap, cb); + } else { + cb(null, null); + } + }, function (data, cb) { + if (foundUserId !== null && foundUserId !== '') { + if (data.length !== 0) { + responseResult.handle = data[0].handle; + responseResult.userId = data[0].id; + sqlParams.userId = data[0].id; + } else { + responseResult.handle = ''; + responseResult.userId = 0; + sqlParams.userId = 0; + } + + api.dataAccess.executeQuery('get_password_by_user_id', sqlParams, dbConnectionMap, cb); + } else { + cb(null, null); + } + }, function (data, cb) { + //found user, redirect to next page + if (foundUserId !== null && foundUserId !== '') { + if (data.length !== 0) { + responseResult.password = data[0].password; + } else { + responseResult.password = ''; + } + + responseResult.result = LOGIN; + responseResult.nextPage = nextPage; + responseResult.socialAccount = socialAccount; + + connection.response = responseResult; + next(connection, true); + return; + } + + //did not find the user, redirect to register page + responseResult.regUrl = "http://www.topcoder.com/?action=callback#access_token=" + + socialAccount.accessToken + "&id_token=" + + socialAccount.jsonWebToken + + "&token_type=bearer&state=http%3A%2F%2Fwww.topcoder.com"; + + responseResult.nextPage = nextPage; + responseResult.result = REGISTER; + responseResult.socialAccount = socialAccount; + connection.response = responseResult; + cb(); + } + + ], function (err) { + if (err) { + helper.handleError(api, connection, err); + } + + next(connection, true); + }); + } +}; \ No newline at end of file diff --git a/apiary.apib b/apiary.apib index d98561495..ec1d60079 100644 --- a/apiary.apib +++ b/apiary.apib @@ -7898,3 +7898,104 @@ Payments APIs "value":"503", "description":"Servers are up but overloaded. Try again later." } + +## Auth0 Callback API [/auth0/callback] +### Auth0 Callback API [GET] + ++ Parameters + + code (required, string, `drztXEojbzIYF5mg`) ... The access code. + + state (optional, string, `http%3A%2F%2Ftc.cloud.topcoder.com`) ... The redirect url. + ++ Response 200 (application/json) + + { + "code": "kHmqAt0e99pSfAKx", + "state": "http://tc.cloud.topcoder.com", + "handle": "heffan", + "userId": 132456, + "password": "4EjPjy6o+/C+dqNPnxIy9A==", + "result": "login", + "nextPage": "http://tc.cloud.topcoder.com", + "socialAccount": { + "name": "tc123bk", + "email": "", + "givenName": "", + "familyName": "", + "emailVerified": false, + "providerId": 4, + "enterpriseLogin": false, + "socialUserId": 5644427, + "jsonWebToken": "eyJ0eXAiOi...", + "accessToken": "STjDXB7tfK2sFgQWIgKuuVUVv0ubZB9k3JBnuCKvkpIl5H4fhOSabOScMxAnhaeW" + + } + ++ Response 200 (application/json) + + { + "code": "GE4U7TEzsB4NMe7e", + "state": "http://tc.cloud.topcoder.com", + "regUrl": "http://www.topcoder.com/?action=callback#access_token=wu9tZmWRnLJQ8pq0jVjxTEo3PSxnFGOEVyyWjMvudepwxRL1R5yILKSWmuvWX2J6&id_token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL3RjLTEyMy1iay5hdXRoMC5jb20vIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMDYzNTIyMDEwODcwMTQ1NzgwMTgiLCJhdWQiOiIzRXhqOXZGeGpja1NRN09NRW1VWnYzQWl5a0UwSzVPYSIsImV4cCI6MTM5Njg3ODc1OCwiaWF0IjoxMzk2ODQyNzU4LCJjbGllbnRJRCI6IjNFeGo5dkZ4amNrU1E3T01FbVVadjNBaXlrRTBLNU9hIiwiY3JlYXRlZF9hdCI6IjIwMTQtMDQtMDRUMDg6MTU6NTUuMzY0WiIsImVtYWlsIjoiZmx5dG9qMmVlQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJmYW1pbHlfbmFtZSI6IlplbmciLCJnZW5kZXIiOiJtYWxlIiwiZ2l2ZW5fbmFtZSI6IkVyaWMiLCJpZGVudGl0aWVzIjpbeyJhY2Nlc3NfdG9rZW4iOiJ5YTI5LjEuQUFEdE5fV2VTdF9UMmp2aTdMYV9DTUlGMXBpX1JDS3BkbVRTajBpQU9kM01MZno0M0ItaE5SRHRYZGhOZ0liZV9RIiwicHJvdmlkZXIiOiJnb29nbGUtb2F1dGgyIiwiZXhwaXJlc19pbiI6MzYwMCwidXNlcl9pZCI6IjEwNjM1MjIwMTA4NzAxNDU3ODAxOCIsImNvbm5lY3Rpb24iOiJnb29nbGUtb2F1dGgyIiwiaXNTb2NpYWwiOnRydWV9XSwibG9jYWxlIjoiemgtQ04iLCJuYW1lIjoiRXJpYyBaZW5nIiwibmlja25hbWUiOiJmbHl0b2oyZWUiLCJwaWN0dXJlIjoiaHR0cHM6Ly9saDMuZ29vZ2xldXNlcmNvbnRlbnQuY29tLy1YZFVJcWRNa0NXQS9BQUFBQUFBQUFBSS9BQUFBQUFBQUFBQS80MjUycnNjYnY1TS9waG90by5qcGciLCJ1c2VyX2lkIjoiZ29vZ2xlLW9hdXRoMnwxMDYzNTIyMDEwODcwMTQ1NzgwMTgifQ.0p9nsPLaFtJsnW5W7_Rd7MqH5EEAiALxXwxup4mRxdQ&token_type=bearer&state=http%3A%2F%2Fwww.topcoder.com", + "nextPage": "http://tc.cloud.topcoder.com", + "result": "register", + "socialAccount": { + "name": "", + "email": "email@gmail.com", + "givenName": "name", + "familyName": "name", + "emailVerified": true, + "providerId": 2, + "enterpriseLogin": false, + "socialUserId": "106352201087014578018", + "jsonWebToken": "eyJ0eXAiOi...", + "accessToken": "wu9tZmWRnLJQ8pq0jVjxTEo3PSxnFGOEVyyWjMvudepwxRL1R5yILKSWmuvWX2J6" + } + } + ++ Response 200 (application/json) + + { + "code": "STSYzhWDjVBqjAIN", + "state": "http://tc.cloud.topcoder.com", + "handle": "heffan", + "userId": 132456, + "password": "4EjPjy6o+/C+dqNPnxIy9A==", + "result": "login", + "nextPage": "http://tc.cloud.topcoder.com", + "socialAccount": { + "name": "heffan", + "email": "", + "givenName": "", + "familyName": "", + "emailVerified": false, + "providerId": 50, + "enterpriseLogin": true, + "socialUserId": "132456", + "jsonWebToken": "eyJ0eXAiOi...", + "accessToken": "C6Ayl5Hy2MeMvTIzK7fvsu5I8xhT943UGn9aJRfTPFvikQbPWowVcZW5krwcuDDc" + } + } + ++ Response 401 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"The social account should have at least one valid email or one valid username." + } + ++ Response 400 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Fails to get access token from auth0." + } + ++ Response 400 (application/json) + + { + "name":"Internal Server Error", + "value":"500", + "description":"Fails to get user info from auth0." + } \ No newline at end of file diff --git a/config.js b/config.js index 667c99660..97d245920 100644 --- a/config.js +++ b/config.js @@ -1,8 +1,8 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @author vangavroche, Ghost_141, kurtrips, Sky_, isv, bugbuka - * @version 1.21 + * @author vangavroche, Ghost_141, kurtrips, Sky_, isv, bugbuka, flytoj2ee + * @version 1.22 * changes in 1.1: * - add defaultCacheLifetime parameter * changes in 1.2: @@ -51,6 +51,8 @@ * Changes in 1.21: * - add minPasswordLength and maxPasswordLength * - add resetTokenSuffix + * Changes in 1.22: + * - add auth0 configuration. */ "use strict"; @@ -348,4 +350,10 @@ config.welcomeEmail = { senderName: '[topcoder] API' }; +config.auth0 = { + serverName: process.env.AUTH0_SERVER_NAME || 'http://agile-crag-5056.herokuapp.com', + clientSecret: process.env.AUTH0_CLIENT_SECRET || '80LhxpoArWfAbgiIekJnDOpRVQcIrjBZ8DGnjDLUFdswwkCOI8zaUhGUZ5dr_2fg', + redirectUrl: process.env.AUTH0_REDIRECT_URL || '/v2/auth0/callback' +}; + exports.config = config; diff --git a/docs/Module Assembly - TopCoder NodeJS Auth0 Callback API v1.0.doc b/docs/Module Assembly - TopCoder NodeJS Auth0 Callback API v1.0.doc new file mode 100644 index 000000000..64b0ee374 Binary files /dev/null and b/docs/Module Assembly - TopCoder NodeJS Auth0 Callback API v1.0.doc differ diff --git a/queries/get_password_by_user_id b/queries/get_password_by_user_id new file mode 100644 index 000000000..59e925364 --- /dev/null +++ b/queries/get_password_by_user_id @@ -0,0 +1 @@ +SELECT password FROM security_user WHERE login_id = @userId@ \ No newline at end of file diff --git a/queries/get_password_by_user_id.json b/queries/get_password_by_user_id.json new file mode 100644 index 000000000..459452653 --- /dev/null +++ b/queries/get_password_by_user_id.json @@ -0,0 +1,5 @@ +{ + "name" : "get_password_by_user_id", + "db" : "common_oltp", + "sqlfile" : "get_password_by_user_id" +} \ No newline at end of file diff --git a/queries/get_user_by_handle b/queries/get_user_by_handle new file mode 100644 index 000000000..090f914bd --- /dev/null +++ b/queries/get_user_by_handle @@ -0,0 +1 @@ +SELECT u.user_id as id, u.first_name, u.last_name, u.handle as handle, u.status, u.reg_source AS source, c.comp_country_code AS country, (SELECT e.address FROM user AS u JOIN email AS e ON e.user_id = u.user_id JOIN 'informixoltp':coder AS c ON c.coder_id = u.user_id WHERE u.handle_lower = LOWER('@handle@') AND e.email_type_id = 1 AND e.primary_ind = 1) AS email, (SELECT e.address FROM user AS u JOIN email AS e ON e.user_id = u.user_id JOIN 'informixoltp':coder AS c ON c.coder_id = u.user_id WHERE u.handle_lower = LOWER('@handle@') AND e.email_type_id = 2 AND e.primary_ind = 1) AS second_email FROM user AS u JOIN 'informixoltp':coder AS c ON c.coder_id = u.user_id WHERE u.handle_lower = LOWER('@handle@') \ No newline at end of file diff --git a/queries/get_user_by_handle.json b/queries/get_user_by_handle.json new file mode 100644 index 000000000..431a7daa1 --- /dev/null +++ b/queries/get_user_by_handle.json @@ -0,0 +1,5 @@ +{ + "name" : "get_user_by_handle", + "db" : "common_oltp", + "sqlfile" : "get_user_by_handle" +} \ No newline at end of file diff --git a/queries/get_user_by_user_id b/queries/get_user_by_user_id new file mode 100644 index 000000000..f24c6fab7 --- /dev/null +++ b/queries/get_user_by_user_id @@ -0,0 +1 @@ +SELECT u.user_id as id, u.first_name, u.last_name, u.handle as handle, u.status, u.reg_source AS source, c.comp_country_code AS country, (SELECT e.address FROM email AS e WHERE e.user_id = '@userId@' AND e.email_type_id = 1 AND e.primary_ind = 1) AS email, (SELECT e.address FROM email AS e WHERE e.user_id = '@userId@' AND e.email_type_id = 2 AND e.primary_ind = 1) AS second_email FROM user AS u JOIN 'informixoltp':coder AS c ON c.coder_id = u.user_id WHERE u.user_id = '@userId@' \ No newline at end of file diff --git a/queries/get_user_by_user_id.json b/queries/get_user_by_user_id.json new file mode 100644 index 000000000..d7066de38 --- /dev/null +++ b/queries/get_user_by_user_id.json @@ -0,0 +1,5 @@ +{ + "name" : "get_user_by_user_id", + "db" : "common_oltp", + "sqlfile" : "get_user_by_user_id" +} \ No newline at end of file diff --git a/queries/get_user_id_by_social_account_email b/queries/get_user_id_by_social_account_email new file mode 100644 index 000000000..65a55e12b --- /dev/null +++ b/queries/get_user_id_by_social_account_email @@ -0,0 +1 @@ +SELECT user_id FROM user_social_login WHERE social_login_provider_id = @providerId@ AND social_email = '@email@' AND social_email_verified = '@emailVerified@' \ No newline at end of file diff --git a/queries/get_user_id_by_social_account_email.json b/queries/get_user_id_by_social_account_email.json new file mode 100644 index 000000000..dd8a8b515 --- /dev/null +++ b/queries/get_user_id_by_social_account_email.json @@ -0,0 +1,5 @@ +{ + "name" : "get_user_id_by_social_account_email", + "db" : "common_oltp", + "sqlfile" : "get_user_id_by_social_account_email" +} \ No newline at end of file diff --git a/queries/get_user_id_by_social_account_name b/queries/get_user_id_by_social_account_name new file mode 100644 index 000000000..2484ee6c2 --- /dev/null +++ b/queries/get_user_id_by_social_account_name @@ -0,0 +1 @@ +SELECT user_id FROM user_social_login WHERE social_login_provider_id = @providerId@ AND social_user_name = '@userName@' \ No newline at end of file diff --git a/queries/get_user_id_by_social_account_name.json b/queries/get_user_id_by_social_account_name.json new file mode 100644 index 000000000..0010c783c --- /dev/null +++ b/queries/get_user_id_by_social_account_name.json @@ -0,0 +1,5 @@ +{ + "name" : "get_user_id_by_social_account_name", + "db" : "common_oltp", + "sqlfile" : "get_user_id_by_social_account_name" +} \ No newline at end of file diff --git a/queries/get_user_id_by_social_user_id_and_provider_id b/queries/get_user_id_by_social_user_id_and_provider_id new file mode 100644 index 000000000..7fec0ff0d --- /dev/null +++ b/queries/get_user_id_by_social_user_id_and_provider_id @@ -0,0 +1 @@ +SELECT user_id FROM user_social_login WHERE social_user_id = '@socialUserId@' AND social_login_provider_id = @providerId@ \ No newline at end of file diff --git a/queries/get_user_id_by_social_user_id_and_provider_id.json b/queries/get_user_id_by_social_user_id_and_provider_id.json new file mode 100644 index 000000000..acb9a042d --- /dev/null +++ b/queries/get_user_id_by_social_user_id_and_provider_id.json @@ -0,0 +1,5 @@ +{ + "name" : "get_user_id_by_social_user_id_and_provider_id", + "db" : "common_oltp", + "sqlfile" : "get_user_id_by_social_user_id_and_provider_id" +} \ No newline at end of file diff --git a/queries/update_social_user_id b/queries/update_social_user_id new file mode 100644 index 000000000..18194cb15 --- /dev/null +++ b/queries/update_social_user_id @@ -0,0 +1 @@ +UPDATE user_social_login set social_user_id = '@socialUserId@' WHERE user_id = '@userId@' \ No newline at end of file diff --git a/queries/update_social_user_id.json b/queries/update_social_user_id.json new file mode 100644 index 000000000..b5d6c3596 --- /dev/null +++ b/queries/update_social_user_id.json @@ -0,0 +1,5 @@ +{ + "name" : "update_social_user_id", + "db" : "common_oltp", + "sqlfile" : "update_social_user_id" +} \ No newline at end of file diff --git a/routes.js b/routes.js index 1927a1554..7144d9c26 100755 --- a/routes.js +++ b/routes.js @@ -1,8 +1,8 @@ /* * Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved. * - * @version 1.34 - * @author vangavroche, Sky_, muzehyun, kurtrips, Ghost_141, ecnu_haozi, hesibo, LazyChild, bugbuka, isv + * @version 1.35 + * @author vangavroche, Sky_, muzehyun, kurtrips, Ghost_141, ecnu_haozi, hesibo, LazyChild, bugbuka, isv, flytoj2ee * Changes in 1.1: * - add routes for search challenges * Changes in 1.2: @@ -80,6 +80,8 @@ * - add route for apply develop review opportunities api. * changes in 1.34: * - added route for client active challenge costs + * changes in 1.35: + * - added route for auth0 callback api */ /* --------------------- @@ -227,7 +229,9 @@ exports.routes = { { path: "/:apiVersion/data/reviewOpportunities", action: "getAlgorithmsReviewOpportunities" }, { path: "/:apiVersion/software/reviewers/:contestType", action: "getChallengeReviewers" }, { path: "/:apiVersion/design/statistics/tops/:challengeType", action: "getStudioTops" }, - { path: "/:apiVersion/data/challengetypes", action: "algorithmsChallengeTypes" } + { path: "/:apiVersion/data/challengetypes", action: "algorithmsChallengeTypes" }, + + { path: "/:apiVersion/auth0/callback", action: "auth0Callback" } ].concat(testMethods.get), post: [ // Stub API diff --git a/test/test.auth0ViewUrl.js b/test/test.auth0ViewUrl.js new file mode 100644 index 000000000..444c092ed --- /dev/null +++ b/test/test.auth0ViewUrl.js @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 TopCoder Inc., All Rights Reserved. + * + * @version 1.0 + * @author TCSASSEMBLER + */ +"use strict"; +/*global describe, it, before, beforeEach, after, afterEach */ +/*jslint node: true, vars: true, unparam: true */ + +var request = require('supertest'); +var assert = require('chai').assert; + +/** + * The api end point. + */ +var API_ENDPOINT = process.env.API_ENDPOINT || 'http://localhost:8080'; + +/** + * The test cases for auth0Callback get action. + * It only contains fail test cases here. + * It cannot auto test the cases which get the correct result. Because it needs to use 3 legs login, + * it says, it needs a link to call auth0 widget, forward to auth0 widget, forward to 3rd party + * (git/google/facebook...) login page, input username / password, forward to this callback function. + * Please refer to deployment guide about how to manually test. + */ +describe('Test auth0 Callback View Url', function () { + /** + * Test empty code. Should return error message. + */ + it('should return error when requesting without code parameter', function (done) { + var req = request(API_ENDPOINT) + .get('/v2/auth0/callback') + .expect('Content-Type', /json/) + .expect(200); + + req.end(function (err, resp) { + assert.equal(resp.body.error, 'Error: code is a required parameter for this action'); + done(); + }); + }); + + this.timeout(120000); //it'll call auth0 server, it maybe slow. + + /** + * Test invalid code and state. Should return error message. + */ + it('should return error when requesting with wrong state parameter or state parameter', function (done) { + var req = request(API_ENDPOINT) + .get('/v2/auth0/callback?code=a&state=b') + .expect('Content-Type', /json/) + .expect(400); + + req.end(function (err, resp) { + assert.equal(resp.body.error.details, 'Fails to get access token from auth0.'); + done(); + }); + }); +}); \ No newline at end of file diff --git a/test_file/js/jquery-1.4.1.min.js b/test_file/js/jquery-1.4.1.min.js new file mode 100644 index 000000000..0c7294c90 --- /dev/null +++ b/test_file/js/jquery-1.4.1.min.js @@ -0,0 +1,152 @@ +/*! + * jQuery JavaScript Library v1.4.1 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Jan 25 19:43:33 2010 -0500 + */ +(function(z,v){function la(){if(!c.isReady){try{r.documentElement.doScroll("left")}catch(a){setTimeout(la,1);return}c.ready()}}function Ma(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,i){var j=a.length;if(typeof b==="object"){for(var n in b)X(a,n,b[n],f,e,d);return a}if(d!==v){f=!i&&f&&c.isFunction(d);for(n=0;n-1){i=j.data;i.beforeFilter&&i.beforeFilter[a.type]&&!i.beforeFilter[a.type](a)||f.push(j.selector)}else delete x[o]}i=c(a.target).closest(f, +a.currentTarget);m=0;for(s=i.length;m)[^>]*$|^#([\w-]+)$/,Qa=/^.[^:#\[\.,]*$/,Ra=/\S/,Sa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Ta=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,O=navigator.userAgent, +va=false,P=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,Q=Array.prototype.slice,wa=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(typeof a==="string")if((d=Pa.exec(a))&&(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:r;if(a=Ta.exec(a))if(c.isPlainObject(b)){a=[r.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=ra([d[1]], +[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}}else{if(b=r.getElementById(d[2])){if(b.id!==d[2])return S.find(a);this.length=1;this[0]=b}this.context=r;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=r;a=r.getElementsByTagName(a)}else return!b||b.jquery?(b||S).find(a):c(b).find(a);else if(c.isFunction(a))return S.ready(a);if(a.selector!==v){this.selector=a.selector;this.context=a.context}return c.isArray(a)?this.setArray(a):c.makeArray(a, +this)},selector:"",jquery:"1.4.1",length:0,size:function(){return this.length},toArray:function(){return Q.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){a=c(a||null);a.prevObject=this;a.context=this.context;if(b==="find")a.selector=this.selector+(this.selector?" ":"")+d;else if(b)a.selector=this.selector+"."+b+"("+d+")";return a},setArray:function(a){this.length=0;ba.apply(this,a);return this},each:function(a,b){return c.each(this, +a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(r,c);else P&&P.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(Q.apply(this,arguments),"slice",Q.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice}; +c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,i,j,n;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
a";var e=d.getElementsByTagName("*"),i=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!i)){c.support= +{leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(i.getAttribute("style")),hrefNormalized:i.getAttribute("href")==="/a",opacity:/^0.55$/.test(i.style.opacity),cssFloat:!!i.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:r.createElement("select").appendChild(r.createElement("option")).selected,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null}; +b.type="text/javascript";try{b.appendChild(r.createTextNode("window."+f+"=1;"))}catch(j){}a.insertBefore(b,a.firstChild);if(z[f]){c.support.scriptEval=true;delete z[f]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function n(){c.support.noCloneEvent=false;d.detachEvent("onclick",n)});d.cloneNode(true).fireEvent("onclick")}d=r.createElement("div");d.innerHTML="";a=r.createDocumentFragment();a.appendChild(d.firstChild); +c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var n=r.createElement("div");n.style.width=n.style.paddingLeft="1px";r.body.appendChild(n);c.boxModel=c.support.boxModel=n.offsetWidth===2;r.body.removeChild(n).style.display="none"});a=function(n){var o=r.createElement("div");n="on"+n;var m=n in o;if(!m){o.setAttribute(n,"return;");m=typeof o[n]==="function"}return m};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=i=null}})();c.props= +{"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ua=0,xa={},Va={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var f=a[G],e=c.cache;if(!b&&!f)return null;f||(f=++Ua);if(typeof b==="object"){a[G]=f;e=e[f]=c.extend(true, +{},b)}else e=e[f]?e[f]:typeof d==="undefined"?Va:(e[f]={});if(d!==v){a[G]=f;e[b]=d}return typeof b==="string"?e[b]:e}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==z?xa:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{try{delete a[G]}catch(i){a.removeAttribute&&a.removeAttribute(G)}delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this, +a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===v){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===v&&this.length)f=c.data(this[0],a);return f===v&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d); +return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===v)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]|| +a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var ya=/[\n\t]/g,ca=/\s+/,Wa=/\r/g,Xa=/href|src|style/,Ya=/(button|input)/i,Za=/(button|input|object|select|textarea)/i,$a=/^(a|area)$/i,za=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(o){var m= +c(this);m.addClass(a.call(this,o,m.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===v){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value|| +{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var i=b?d:0;for(d=b?d+1:e.length;i=0;else if(c.nodeName(this,"select")){var x=c.makeArray(s);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),x)>=0});if(!x.length)this.selectedIndex=-1}else this.value=s}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return v;if(f&&b in c.attrFn)return c(a)[b](d); +f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==v;b=f&&c.props[b]||b;if(a.nodeType===1){var i=Xa.test(b);if(b in a&&f&&!i){if(e){b==="type"&&Ya.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Za.test(a.nodeName)||$a.test(a.nodeName)&&a.href?0:v;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText= +""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&i?a.getAttribute(b,2):a.getAttribute(b);return a===null?v:a}return c.style(a,b,d)}});var ab=function(a){return a.replace(/[^\w\s\.\|`]/g,function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==z&&!a.frameElement)a=z;if(!d.guid)d.guid=c.guid++;if(f!==v){d=c.proxy(d);d.data=f}var e=c.data(a,"events")||c.data(a,"events",{}),i=c.data(a,"handle"),j;if(!i){j= +function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(j.elem,arguments):v};i=c.data(a,"handle",j)}if(i){i.elem=a;b=b.split(/\s+/);for(var n,o=0;n=b[o++];){var m=n.split(".");n=m.shift();if(o>1){d=c.proxy(d);if(f!==v)d.data=f}d.type=m.slice(0).sort().join(".");var s=e[n],x=this.special[n]||{};if(!s){s=e[n]={};if(!x.setup||x.setup.call(a,f,m,d)===false)if(a.addEventListener)a.addEventListener(n,i,false);else a.attachEvent&&a.attachEvent("on"+n,i)}if(x.add)if((m=x.add.call(a, +d,f,m,s))&&c.isFunction(m)){m.guid=m.guid||d.guid;m.data=m.data||d.data;m.type=m.type||d.type;d=m}s[d.guid]=d;this.global[n]=true}a=null}}},global:{},remove:function(a,b,d){if(!(a.nodeType===3||a.nodeType===8)){var f=c.data(a,"events"),e,i,j;if(f){if(b===v||typeof b==="string"&&b.charAt(0)===".")for(i in f)this.remove(a,i+(b||""));else{if(b.type){d=b.handler;b=b.type}b=b.split(/\s+/);for(var n=0;i=b[n++];){var o=i.split(".");i=o.shift();var m=!o.length,s=c.map(o.slice(0).sort(),ab);s=new RegExp("(^|\\.)"+ +s.join("\\.(?:.*\\.)?")+"(\\.|$)");var x=this.special[i]||{};if(f[i]){if(d){j=f[i][d.guid];delete f[i][d.guid]}else for(var A in f[i])if(m||s.test(f[i][A].type))delete f[i][A];x.remove&&x.remove.call(a,o,j);for(e in f[i])break;if(!e){if(!x.teardown||x.teardown.call(a,o)===false)if(a.removeEventListener)a.removeEventListener(i,c.data(a,"handle"),false);else a.detachEvent&&a.detachEvent("on"+i,c.data(a,"handle"));e=null;delete f[i]}}}}for(e in f)break;if(!e){if(A=c.data(a,"handle"))A.elem=null;c.removeData(a, +"events");c.removeData(a,"handle")}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();this.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return v;a.result=v;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d, +b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(i){}if(!a.isPropagationStopped()&&f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){d=a.target;var j;if(!(c.nodeName(d,"a")&&e==="click")&&!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()])){try{if(d[e]){if(j=d["on"+e])d["on"+e]=null;this.triggered=true;d[e]()}}catch(n){}if(j)d["on"+e]=j;this.triggered=false}}},handle:function(a){var b, +d;a=arguments[0]=c.event.fix(a||z.event);a.currentTarget=this;d=a.type.split(".");a.type=d.shift();b=!d.length&&!a.exclusive;var f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)");d=(c.data(this,"events")||{})[a.type];for(var e in d){var i=d[e];if(b||f.test(i.type)){a.handler=i;a.data=i.data;i=i.apply(this,arguments);if(i!==v){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), +fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||r;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=r.documentElement;d=r.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop|| +d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==v)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a,b){c.extend(a,b||{});a.guid+=b.selector+b.live;b.liveProxy=a;c.event.add(this,b.live,na,b)},remove:function(a){if(a.length){var b= +0,d=new RegExp("(^|\\.)"+a[0]+"(\\.|$)");c.each(c.data(this,"events").live||{},function(){d.test(this.type)&&b++});b<1&&c.event.remove(this,a[0],na)}},special:{}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true}; +c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y};var Aa=function(a){for(var b= +a.relatedTarget;b&&b!==this;)try{b=b.parentNode}catch(d){break}if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}},Ba=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ba:Aa,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ba:Aa)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(a,b,d){if(this.nodeName.toLowerCase()!== +"form"){c.event.add(this,"click.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="submit"||i==="image")&&c(e).closest("form").length)return ma("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="text"||i==="password")&&c(e).closest("form").length&&f.keyCode===13)return ma("submit",this,arguments)})}else return false},remove:function(a,b){c.event.remove(this,"click.specialSubmit"+(b?"."+b.guid:""));c.event.remove(this, +"keypress.specialSubmit"+(b?"."+b.guid:""))}};if(!c.support.changeBubbles){var da=/textarea|input|select/i;function Ca(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d}function ea(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Ca(d);if(a.type!=="focusout"|| +d.type!=="radio")c.data(d,"_change_data",e);if(!(f===v||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}}c.event.special.change={filters:{focusout:ea,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return ea.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return ea.call(this,a)},beforeactivate:function(a){a= +a.target;a.nodeName.toLowerCase()==="input"&&a.type==="radio"&&c.data(a,"_change_data",Ca(a))}},setup:function(a,b,d){for(var f in T)c.event.add(this,f+".specialChange."+d.guid,T[f]);return da.test(this.nodeName)},remove:function(a,b){for(var d in T)c.event.remove(this,d+".specialChange"+(b?"."+b.guid:""),T[d]);return da.test(this.nodeName)}};var T=c.event.special.change.filters}r.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this, +f)}c.event.special[b]={setup:function(){this.addEventListener(a,d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var i in d)this[b](i,f,d[i],e);return this}if(c.isFunction(f)){e=f;f=v}var j=b==="one"?c.proxy(e,function(n){c(this).unbind(n,j);return e.apply(this,arguments)}):e;return d==="unload"&&b!=="one"?this.one(d,f,e):this.each(function(){c.event.add(this,d,j,f)})}});c.fn.extend({unbind:function(a, +b){if(typeof a==="object"&&!a.preventDefault){for(var d in a)this.unbind(d,a[d]);return this}return this.each(function(){c.event.remove(this,a,b)})},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},toggle:function(a){for(var b=arguments,d=1;d0){y=t;break}}t=t[g]}l[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,i=Object.prototype.toString,j=false,n=true;[0,0].sort(function(){n=false;return 0});var o=function(g,h,k,l){k=k||[];var q=h=h||r;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g|| +typeof g!=="string")return k;for(var p=[],u,t,y,R,H=true,M=w(h),I=g;(f.exec(""),u=f.exec(I))!==null;){I=u[3];p.push(u[1]);if(u[2]){R=u[3];break}}if(p.length>1&&s.exec(g))if(p.length===2&&m.relative[p[0]])t=fa(p[0]+p[1],h);else for(t=m.relative[p[0]]?[h]:o(p.shift(),h);p.length;){g=p.shift();if(m.relative[g])g+=p.shift();t=fa(g,t)}else{if(!l&&p.length>1&&h.nodeType===9&&!M&&m.match.ID.test(p[0])&&!m.match.ID.test(p[p.length-1])){u=o.find(p.shift(),h,M);h=u.expr?o.filter(u.expr,u.set)[0]:u.set[0]}if(h){u= +l?{expr:p.pop(),set:A(l)}:o.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=u.expr?o.filter(u.expr,u.set):u.set;if(p.length>0)y=A(t);else H=false;for(;p.length;){var D=p.pop();u=D;if(m.relative[D])u=p.pop();else D="";if(u==null)u=h;m.relative[D](y,u,M)}}else y=[]}y||(y=t);y||o.error(D||g);if(i.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))k.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&& +y[g].nodeType===1&&k.push(t[g]);else k.push.apply(k,y);else A(y,k);if(R){o(R,q,k,l);o.uniqueSort(k)}return k};o.uniqueSort=function(g){if(C){j=n;g.sort(C);if(j)for(var h=1;h":function(g,h){var k=typeof h==="string";if(k&&!/\W/.test(h)){h=h.toLowerCase();for(var l=0,q=g.length;l=0))k||l.push(u);else if(k)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&& +"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,k,l,q,p){h=g[1].replace(/\\/g,"");if(!p&&m.attrMap[h])g[1]=m.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,k,l,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=o(g[3],null,null,h);else{g=o.filter(g[3],h,k,true^q);k||l.push.apply(l,g);return false}else if(m.match.POS.test(g[0])||m.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true); +return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,k){return!!o(k[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"=== +g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,h){return h===0},last:function(g,h,k,l){return h===l.length-1},even:function(g,h){return h%2=== +0},odd:function(g,h){return h%2===1},lt:function(g,h,k){return hk[3]-0},nth:function(g,h,k){return k[3]-0===h},eq:function(g,h,k){return k[3]-0===h}},filter:{PSEUDO:function(g,h,k,l){var q=h[1],p=m.filters[q];if(p)return p(g,k,h,l);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=h[3];k=0;for(l=h.length;k= +0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var k=h[1];g=m.attrHandle[k]?m.attrHandle[k](g):g[k]!=null?g[k]:g.getAttribute(k);k=g+"";var l=h[2];h=h[4];return g==null?l==="!=":l==="="?k===h:l==="*="?k.indexOf(h)>=0:l==="~="?(" "+k+" ").indexOf(h)>=0:!h?k&&g!==false:l==="!="?k!==h:l==="^="? +k.indexOf(h)===0:l==="$="?k.substr(k.length-h.length)===h:l==="|="?k===h||k.substr(0,h.length+1)===h+"-":false},POS:function(g,h,k,l){var q=m.setFilters[h[2]];if(q)return q(g,k,h,l)}}},s=m.match.POS;for(var x in m.match){m.match[x]=new RegExp(m.match[x].source+/(?![^\[]*\])(?![^\(]*\))/.source);m.leftMatch[x]=new RegExp(/(^(?:.|\r|\n)*?)/.source+m.match[x].source.replace(/\\(\d+)/g,function(g,h){return"\\"+(h-0+1)}))}var A=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g}; +try{Array.prototype.slice.call(r.documentElement.childNodes,0)}catch(B){A=function(g,h){h=h||[];if(i.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var k=0,l=g.length;k";var k=r.documentElement;k.insertBefore(g,k.firstChild);if(r.getElementById(h)){m.find.ID=function(l,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(l[1]))?q.id===l[1]||typeof q.getAttributeNode!=="undefined"&&q.getAttributeNode("id").nodeValue===l[1]?[q]:v:[]};m.filter.ID=function(l,q){var p=typeof l.getAttributeNode!=="undefined"&&l.getAttributeNode("id"); +return l.nodeType===1&&p&&p.nodeValue===q}}k.removeChild(g);k=g=null})();(function(){var g=r.createElement("div");g.appendChild(r.createComment(""));if(g.getElementsByTagName("*").length>0)m.find.TAG=function(h,k){k=k.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var l=0;k[l];l++)k[l].nodeType===1&&h.push(k[l]);k=h}return k};g.innerHTML="";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")m.attrHandle.href=function(h){return h.getAttribute("href", +2)};g=null})();r.querySelectorAll&&function(){var g=o,h=r.createElement("div");h.innerHTML="

";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){o=function(l,q,p,u){q=q||r;if(!u&&q.nodeType===9&&!w(q))try{return A(q.querySelectorAll(l),p)}catch(t){}return g(l,q,p,u)};for(var k in g)o[k]=g[k];h=null}}();(function(){var g=r.createElement("div");g.innerHTML="
";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length=== +0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){m.order.splice(1,0,"CLASS");m.find.CLASS=function(h,k,l){if(typeof k.getElementsByClassName!=="undefined"&&!l)return k.getElementsByClassName(h[1])};g=null}}})();var E=r.compareDocumentPosition?function(g,h){return g.compareDocumentPosition(h)&16}:function(g,h){return g!==h&&(g.contains?g.contains(h):true)},w=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},fa=function(g,h){var k=[], +l="",q;for(h=h.nodeType?[h]:h;q=m.match.PSEUDO.exec(g);){l+=q[0];g=g.replace(m.match.PSEUDO,"")}g=m.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var i=d;i0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,i={},j;if(f&&a.length){e=0;for(var n=a.length;e +-1:c(f).is(e)){d.push({selector:j,elem:f});delete i[j]}}f=f.parentNode}}return d}var o=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(m,s){for(;s&&s.ownerDocument&&s!==b;){if(o?o.index(s)>-1:c(s).is(a))return s;s=s.parentNode}return null})},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(), +a);return this.pushStack(pa(a[0])||pa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")}, +nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);bb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e): +e;if((this.length>1||db.test(f))&&cb.test(a))e=e.reverse();return this.pushStack(e,a,Q.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===v||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!== +b&&d.push(a);return d}});var Fa=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ga=/(<([\w:]+)[^>]*?)\/>/g,eb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,Ha=/<([\w:]+)/,fb=/"},F={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"], +col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==v)return this.empty().append((this[0]&&this[0].ownerDocument||r).createTextNode(a));return c.getText(this)}, +wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length? +d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments, +false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&& +!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Fa,"").replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){qa(this,b);qa(this.find("*"),b.find("*"))}return b},html:function(a){if(a===v)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Fa,""):null;else if(typeof a==="string"&&!/ + + + + + + + + + +