Skip to content

Commit 57691c0

Browse files
author
Santiago Gonzalez
committed
Merge branch 'dev' into sagonzal/msal-node-authority
2 parents 2c463be + 34b4733 commit 57691c0

File tree

10 files changed

+341
-84
lines changed

10 files changed

+341
-84
lines changed

lib/msal-core/src/UserAgentApplication.ts

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1106,23 +1106,24 @@ export class UserAgentApplication {
11061106
// if set to navigate to loginRequest page post login
11071107
if (this.config.auth.navigateToLoginRequestUrl && window.parent === window) {
11081108
const loginRequestUrl = this.cacheStorage.getItem(`${TemporaryCacheKeys.LOGIN_REQUEST}${Constants.resourceDelimiter}${stateInfo.state}`, this.inCookie);
1109-
const currentUrl = UrlUtils.getCurrentUrl();
11101109

11111110
// Redirect to home page if login request url is null (real null or the string null)
11121111
if (!loginRequestUrl || loginRequestUrl === "null") {
11131112
this.logger.error("Unable to get valid login request url from cache, redirecting to home page");
1114-
window.location.href = "/";
1113+
window.location.assign("/");
11151114
return;
1116-
} else if (currentUrl !== loginRequestUrl) {
1117-
// If loginRequestUrl contains a hash (e.g. Angular routing), process the hash now then redirect to prevent both hashes in url
1118-
if (loginRequestUrl.indexOf("#") > -1) {
1119-
this.logger.info("loginRequestUrl contains hash, processing response hash immediately then redirecting");
1120-
this.processCallBack(hash, stateInfo, null);
1121-
window.location.href = loginRequestUrl;
1115+
} else {
1116+
const currentUrl = UrlUtils.removeHashFromUrl(window.location.href);
1117+
const finalRedirectUrl = UrlUtils.removeHashFromUrl(loginRequestUrl);
1118+
if (currentUrl !== finalRedirectUrl) {
1119+
window.location.assign(`${finalRedirectUrl}${hash}`);
1120+
return;
11221121
} else {
1123-
window.location.href = `${loginRequestUrl}${hash}`;
1122+
const loginRequestUrlComponents = UrlUtils.GetUrlComponents(loginRequestUrl);
1123+
if (loginRequestUrlComponents.Hash){
1124+
window.location.hash = loginRequestUrlComponents.Hash;
1125+
}
11241126
}
1125-
return;
11261127
}
11271128
}
11281129

lib/msal-core/src/utils/UrlUtils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ export class UrlUtils {
102102
return window.location.href.split("?")[0].split("#")[0];
103103
}
104104

105+
/**
106+
* Returns given URL with query string removed
107+
*/
108+
static removeHashFromUrl(url: string): string {
109+
return url.split("#")[0];
110+
}
111+
105112
/**
106113
* Given a url like https://a:b/common/d?e=f#g, and a tenantId, returns https://a:b/tenantId/d
107114
* @param href The url
@@ -148,6 +155,14 @@ export class UrlUtils {
148155
let pathSegments = urlComponents.AbsolutePath.split("/");
149156
pathSegments = pathSegments.filter((val) => val && val.length > 0); // remove empty elements
150157
urlComponents.PathSegments = pathSegments;
158+
159+
if (match[6]){
160+
urlComponents.Search = match[6];
161+
}
162+
if (match[8]){
163+
urlComponents.Hash = match[8];
164+
}
165+
151166
return urlComponents;
152167
}
153168

lib/msal-core/test/UserAgentApplication.spec.ts

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1212,10 +1212,14 @@ describe("UserAgentApplication.ts Class", function () {
12121212

12131213
setAuthInstanceStubs();
12141214
setTestCacheItems();
1215+
1216+
delete window.location;
1217+
window.location = {
1218+
...oldWindowLocation
1219+
};
12151220
});
12161221

12171222
afterEach(function() {
1218-
window.location.hash = "";
12191223
config = {auth: {clientId: ""}};
12201224
cacheStorage.clear();
12211225
sinon.restore();
@@ -1241,6 +1245,196 @@ describe("UserAgentApplication.ts Class", function () {
12411245
msal.handleRedirectCallback(checkRespFromServer, errorReceivedCallback);
12421246
});
12431247

1248+
it("tests navigation to loginRequestUrl after first redirect", function(done) {
1249+
config.auth.navigateToLoginRequestUrl = true;
1250+
const loginStartPage = "http://localhost:8081/test/"
1251+
const successHash = testHashesForState(TEST_LIBRARY_STATE).TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM;
1252+
1253+
window.location = {
1254+
...oldWindowLocation,
1255+
assign: function (url) {
1256+
try {
1257+
expect(url).to.equal(loginStartPage + successHash);
1258+
done();
1259+
} catch (e) {
1260+
console.error(e);
1261+
}
1262+
},
1263+
href: "http://localhost:8081/"
1264+
};
1265+
1266+
sinon.stub(window, "parent").returns(window);
1267+
1268+
window.location.hash = successHash;
1269+
cacheStorage.setItem(`${TemporaryCacheKeys.LOGIN_REQUEST}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, loginStartPage);
1270+
cacheStorage.setItem(`${TemporaryCacheKeys.STATE_LOGIN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, `${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`);
1271+
cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, TEST_NONCE);
1272+
1273+
msal = new UserAgentApplication(config);
1274+
});
1275+
1276+
it("tests navigation to loginRequestUrl after first redirect", function(done) {
1277+
config.auth.navigateToLoginRequestUrl = true;
1278+
const baseStartUrl = "http://localhost:8081/test/"
1279+
const loginStartPage = baseStartUrl + "#testHash"
1280+
const successHash = testHashesForState(TEST_LIBRARY_STATE).TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM;
1281+
1282+
window.location = {
1283+
...oldWindowLocation,
1284+
assign: function (url) {
1285+
try {
1286+
expect(url).to.equal(baseStartUrl + successHash);
1287+
done();
1288+
} catch (e) {
1289+
console.error(e);
1290+
}
1291+
},
1292+
href: "http://localhost:8081/"
1293+
};
1294+
1295+
sinon.stub(window, "parent").returns(window);
1296+
1297+
window.location.hash = successHash;
1298+
cacheStorage.setItem(`${TemporaryCacheKeys.LOGIN_REQUEST}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, loginStartPage);
1299+
cacheStorage.setItem(`${TemporaryCacheKeys.STATE_LOGIN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, `${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`);
1300+
cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, TEST_NONCE);
1301+
1302+
msal = new UserAgentApplication(config);
1303+
});
1304+
1305+
it("tests navigation to loginRequestUrl inc. user querystring after first redirect", function(done) {
1306+
config.auth.navigateToLoginRequestUrl = true;
1307+
const baseStartUrl = "http://localhost:8081/test/"
1308+
const loginStartPage = baseStartUrl + "?testKey=testVal"
1309+
const successHash = testHashesForState(TEST_LIBRARY_STATE).TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM;
1310+
1311+
window.location = {
1312+
...oldWindowLocation,
1313+
assign: function (url) {
1314+
try {
1315+
expect(url).to.equal(loginStartPage + successHash);
1316+
done();
1317+
} catch (e) {
1318+
console.error(e);
1319+
}
1320+
},
1321+
href: "http://localhost:8081/"
1322+
};
1323+
1324+
sinon.stub(window, "parent").returns(window);
1325+
1326+
window.location.hash = successHash;
1327+
cacheStorage.setItem(`${TemporaryCacheKeys.LOGIN_REQUEST}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, loginStartPage);
1328+
cacheStorage.setItem(`${TemporaryCacheKeys.STATE_LOGIN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, `${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`);
1329+
cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, TEST_NONCE);
1330+
1331+
msal = new UserAgentApplication(config);
1332+
});
1333+
1334+
it("tests user hash is added back to url on final page and token response is cached", function() {
1335+
config.auth.navigateToLoginRequestUrl = true;
1336+
const loginUrl = "http://localhost:8081/test/"
1337+
const userHash = "#testHash"
1338+
const loginStartPage = loginUrl + userHash
1339+
const successHash = testHashesForState(TEST_LIBRARY_STATE).TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM;
1340+
1341+
window.location.href = loginUrl;
1342+
1343+
sinon.stub(window, "parent").returns(window);
1344+
sinon.stub(window.location, "href").returns(loginStartPage + successHash)
1345+
1346+
window.location.hash = successHash;
1347+
cacheStorage.setItem(`${TemporaryCacheKeys.LOGIN_REQUEST}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, loginStartPage);
1348+
cacheStorage.setItem(`${TemporaryCacheKeys.STATE_LOGIN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, `${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`);
1349+
cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, TEST_NONCE);
1350+
1351+
expect(window.location.href).to.equal(loginUrl);
1352+
expect(window.location.hash).to.equal(successHash);
1353+
msal = new UserAgentApplication(config);
1354+
expect(window.location.href).to.equal(loginUrl);
1355+
expect(window.location.hash).to.equal(userHash);
1356+
expect(cacheStorage.getItem(PersistentCacheKeys.IDTOKEN)).to.equal(TEST_TOKENS.IDTOKEN_V2);
1357+
});
1358+
1359+
it("tests user query string present on final page url and token response is cached", function() {
1360+
config.auth.navigateToLoginRequestUrl = true;
1361+
const loginUrl = "http://localhost:8081/test/"
1362+
const userQueryString = "?testKey=testVal"
1363+
const loginStartPage = loginUrl + userQueryString;
1364+
const successHash = testHashesForState(TEST_LIBRARY_STATE).TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM;
1365+
1366+
window.location.href = loginStartPage;
1367+
window.location.search = userQueryString;
1368+
1369+
sinon.stub(window, "parent").returns(window);
1370+
sinon.stub(window.location, "href").returns(loginStartPage + successHash)
1371+
1372+
window.location.hash = successHash;
1373+
cacheStorage.setItem(`${TemporaryCacheKeys.LOGIN_REQUEST}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, loginStartPage);
1374+
cacheStorage.setItem(`${TemporaryCacheKeys.STATE_LOGIN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, `${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`);
1375+
cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, TEST_NONCE);
1376+
1377+
expect(window.location.href).to.equal(loginStartPage);
1378+
expect(window.location.hash).to.equal(successHash);
1379+
expect(window.location.search).to.equal(userQueryString);
1380+
msal = new UserAgentApplication(config);
1381+
expect(window.location.href).to.equal(loginStartPage);
1382+
expect(window.location.hash).to.equal("");
1383+
expect(window.location.search).to.equal(userQueryString);
1384+
expect(cacheStorage.getItem(PersistentCacheKeys.IDTOKEN)).to.equal(TEST_TOKENS.IDTOKEN_V2);
1385+
});
1386+
1387+
it("tests user hash is added back to url and query string exists on final page url and token response is cached", function() {
1388+
config.auth.navigateToLoginRequestUrl = true;
1389+
const loginUrl = "http://localhost:8081/test/"
1390+
const userQueryString = "?testKey=testVal"
1391+
const userHash = "#testHash"
1392+
const loginStartPage = loginUrl + userQueryString + userHash;
1393+
const successHash = testHashesForState(TEST_LIBRARY_STATE).TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM;
1394+
1395+
window.location.href = loginUrl + userQueryString;
1396+
window.location.search = userQueryString;
1397+
1398+
sinon.stub(window, "parent").returns(window);
1399+
sinon.stub(window.location, "href").returns(loginStartPage + successHash)
1400+
1401+
window.location.hash = successHash;
1402+
cacheStorage.setItem(`${TemporaryCacheKeys.LOGIN_REQUEST}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, loginStartPage);
1403+
cacheStorage.setItem(`${TemporaryCacheKeys.STATE_LOGIN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, `${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`);
1404+
cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, TEST_NONCE);
1405+
1406+
expect(window.location.href).to.equal(loginUrl + userQueryString);
1407+
expect(window.location.hash).to.equal(successHash);
1408+
expect(window.location.search).to.equal(userQueryString);
1409+
msal = new UserAgentApplication(config);
1410+
expect(window.location.href).to.equal(loginUrl + userQueryString);
1411+
expect(window.location.hash).to.equal(userHash);
1412+
expect(window.location.search).to.equal(userQueryString);
1413+
expect(cacheStorage.getItem(PersistentCacheKeys.IDTOKEN)).to.equal(TEST_TOKENS.IDTOKEN_V2);
1414+
});
1415+
1416+
it("tests navigation to homepage after first redirect if loginStartPage not set", function(done) {
1417+
config.auth.navigateToLoginRequestUrl = true;
1418+
const successHash = testHashesForState(TEST_LIBRARY_STATE).TEST_SUCCESS_ID_TOKEN_HASH + TEST_USER_STATE_NUM;
1419+
1420+
window.location.assign = function (url) {
1421+
try {
1422+
expect(url).to.equal("/");
1423+
done();
1424+
} catch (e) {
1425+
console.error(e);
1426+
}
1427+
};
1428+
1429+
sinon.stub(window, "parent").returns(window);
1430+
1431+
window.location.hash = successHash;
1432+
cacheStorage.setItem(`${TemporaryCacheKeys.STATE_LOGIN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, `${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`);
1433+
cacheStorage.setItem(`${TemporaryCacheKeys.NONCE_IDTOKEN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, TEST_NONCE);
1434+
1435+
msal = new UserAgentApplication(config);
1436+
});
1437+
12441438
it("tests saveTokenForHash in case of error", function(done) {
12451439
window.location.hash = testHashesForState(TEST_LIBRARY_STATE).TEST_ERROR_HASH + TEST_USER_STATE_NUM;
12461440
cacheStorage.setItem(`${TemporaryCacheKeys.STATE_LOGIN}|${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`, `${TEST_LIBRARY_STATE}|${TEST_USER_STATE_NUM}`);

lib/msal-core/test/utils/UrlUtils.spec.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { TEST_CONFIG, TEST_RESPONSE_TYPE, TEST_URIS } from "../TestConstants";
55
import { AuthorityFactory } from "../../src/authority/AuthorityFactory";
66
import { ServerRequestParameters } from "../../src/ServerRequestParameters";
77
import { ServerHashParamKeys, Constants } from "../../src/utils/Constants";
8+
import { IUri } from "../../src/IUri";
9+
import { IdToken } from "../../src/IdToken";
810

911
describe("UrlUtils.ts class", () => {
1012

@@ -108,4 +110,85 @@ describe("UrlUtils.ts class", () => {
108110
expect(stateParts[1]).to.equal("hello");
109111
});
110112
})
113+
114+
describe("getUrlComponents", () => {
115+
let url;
116+
117+
beforeEach(() => {
118+
url = "https://localhost:30662/";
119+
});
120+
121+
it("properly splits up basic url", () => {
122+
const urlComponents = UrlUtils.GetUrlComponents(url);
123+
124+
expect(urlComponents.Protocol).to.equal("https:");
125+
expect(urlComponents.HostNameAndPort).to.equal("localhost:30662");
126+
expect(urlComponents.AbsolutePath).to.equal("/");
127+
});
128+
129+
it("properly splits up url with path", () => {
130+
url += "testPage1/testPage2/"
131+
const urlComponents = UrlUtils.GetUrlComponents(url);
132+
133+
expect(urlComponents.Protocol).to.equal("https:");
134+
expect(urlComponents.HostNameAndPort).to.equal("localhost:30662");
135+
expect(urlComponents.AbsolutePath).to.equal("/testPage1/testPage2/");
136+
});
137+
138+
it("properly splits up url with query string", () => {
139+
url += "?testkey1=testval1&testkey2=testval2"
140+
const urlComponents = UrlUtils.GetUrlComponents(url);
141+
142+
expect(urlComponents.Protocol).to.equal("https:");
143+
expect(urlComponents.HostNameAndPort).to.equal("localhost:30662");
144+
expect(urlComponents.AbsolutePath).to.equal("/");
145+
expect(urlComponents.Search).to.equal("?testkey1=testval1&testkey2=testval2");
146+
});
147+
148+
it("properly splits up url with hash", () => {
149+
url += "#testhash"
150+
const urlComponents = UrlUtils.GetUrlComponents(url);
151+
152+
expect(urlComponents.Protocol).to.equal("https:");
153+
expect(urlComponents.HostNameAndPort).to.equal("localhost:30662");
154+
expect(urlComponents.AbsolutePath).to.equal("/");
155+
expect(urlComponents.Hash).to.equal("#testhash");
156+
});
157+
158+
it("properly splits up url with hash and query string", () => {
159+
url += "?testkey1=testval1&testkey2=testval2"
160+
url += "#testhash"
161+
const urlComponents = UrlUtils.GetUrlComponents(url);
162+
163+
expect(urlComponents.Protocol).to.equal("https:");
164+
expect(urlComponents.HostNameAndPort).to.equal("localhost:30662");
165+
expect(urlComponents.AbsolutePath).to.equal("/");
166+
expect(urlComponents.Search).to.equal("?testkey1=testval1&testkey2=testval2");
167+
expect(urlComponents.Hash).to.equal("#testhash");
168+
});
169+
});
170+
171+
describe("removeHashFromUrl", () => {
172+
const url = "https://localhost:30662/";
173+
174+
it("returns same url if hash not present in url", () => {
175+
expect(UrlUtils.removeHashFromUrl(url)).to.eq(url);
176+
});
177+
178+
it("returns base url if hash is present in url", () => {
179+
const testUrl = url + "#testHash";
180+
expect(UrlUtils.removeHashFromUrl(testUrl)).to.eq(url);
181+
});
182+
183+
it("returns url with query string if hash not present on url", () => {
184+
const testUrl = url + "?testPage=1";
185+
expect(UrlUtils.removeHashFromUrl(testUrl)).to.eq(testUrl);
186+
});
187+
188+
it("returns url with query string if both hash and query string present in url", () => {
189+
const urlWithQueryString = url + "?testPage=1";
190+
const testUrl = urlWithQueryString + "#testHash";
191+
expect(UrlUtils.removeHashFromUrl(testUrl)).to.eq(urlWithQueryString);
192+
});
193+
});
111194
});

0 commit comments

Comments
 (0)