MOBILE-3286 login: Improve guessing site domains
parent
2e69497c13
commit
a476a773f1
|
@ -27,6 +27,11 @@ interface UrlParts {
|
||||||
*/
|
*/
|
||||||
domain?: string;
|
domain?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Url port.
|
||||||
|
*/
|
||||||
|
port?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Url path.
|
* Url path.
|
||||||
*/
|
*/
|
||||||
|
@ -49,39 +54,69 @@ interface UrlParts {
|
||||||
*/
|
*/
|
||||||
export class CoreUrl {
|
export class CoreUrl {
|
||||||
|
|
||||||
// Avoid creating singleton instances
|
// Avoid creating singleton instances.
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse parts of a url, using an implicit protocol if it is missing from the url.
|
* Parse parts of a url, using an implicit protocol if it is missing from the url.
|
||||||
*
|
*
|
||||||
* @param url Url.
|
* @param url Url.
|
||||||
* @param implicitProtocol Protocol to be used if the url doesn't have any.
|
|
||||||
* @return Url parts.
|
* @return Url parts.
|
||||||
*/
|
*/
|
||||||
static parse(url: string, implicitProtocol?: string): UrlParts | null {
|
static parse(url: string): UrlParts | null {
|
||||||
// Prepare url before parsing
|
// Parse url with regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B.
|
||||||
url = url.trim();
|
|
||||||
|
|
||||||
if (implicitProtocol && !url.match(/^[a-zA-Z]+:\/\//)) {
|
|
||||||
url = `${implicitProtocol}://${url}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B
|
|
||||||
const match = url.trim().match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);
|
const match = url.trim().match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare parts replacing empty strings with undefined
|
// Split host into domain and port.
|
||||||
|
const host = match[4] || '';
|
||||||
|
const [domain, port]: string[] = host.indexOf(':') === -1 ? [host] : host.split(':');
|
||||||
|
|
||||||
|
// Prepare parts replacing empty strings with undefined.
|
||||||
return {
|
return {
|
||||||
protocol: match[2] || undefined,
|
protocol: match[2] || undefined,
|
||||||
domain: match[4] || undefined,
|
domain: domain || undefined,
|
||||||
|
port: port || undefined,
|
||||||
path: match[5] || undefined,
|
path: match[5] || undefined,
|
||||||
query: match[7] || undefined,
|
query: match[7] || undefined,
|
||||||
fragment: match[9] || undefined,
|
fragment: match[9] || undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guess the Moodle domain from a site url.
|
||||||
|
*
|
||||||
|
* @param url Site url.
|
||||||
|
* @return Guessed Moodle domain.
|
||||||
|
*/
|
||||||
|
static guessMoodleDomain(url: string): string | null {
|
||||||
|
// Add protocol if it was missing. Moodle can only be served through http or https, so this is a fair assumption to make.
|
||||||
|
if (!url.match(/^https?:\/\//)) {
|
||||||
|
url = `https://${url}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match using common suffixes.
|
||||||
|
const knownSuffixes = [
|
||||||
|
'\/my\/?',
|
||||||
|
'\/\\\?redirect=0',
|
||||||
|
'\/index\\\.php',
|
||||||
|
'\/course\/view\\\.php',
|
||||||
|
'\/login\/index\\\.php',
|
||||||
|
'\/mod\/page\/view\\\.php',
|
||||||
|
];
|
||||||
|
const match = url.match(new RegExp(`^https?:\/\/(.*?)(${knownSuffixes.join('|')})`));
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
return match[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If nothing else worked, parse the domain.
|
||||||
|
const urlParts = CoreUrl.parse(url);
|
||||||
|
|
||||||
|
return urlParts && urlParts.domain ? urlParts.domain : null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,8 @@ export class CoreLoginSitePage {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
url = url.trim();
|
||||||
|
|
||||||
const modal = this.domUtils.showModalLoading(),
|
const modal = this.domUtils.showModalLoading(),
|
||||||
siteData = this.sitesProvider.getDemoSiteData(url);
|
siteData = this.sitesProvider.getDemoSiteData(url);
|
||||||
|
|
||||||
|
@ -113,18 +115,12 @@ export class CoreLoginSitePage {
|
||||||
} else {
|
} else {
|
||||||
// Not a demo site.
|
// Not a demo site.
|
||||||
this.sitesProvider.checkSite(url)
|
this.sitesProvider.checkSite(url)
|
||||||
|
|
||||||
// Attempt parsing the domain after initial check failed
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
const urlParts = CoreUrl.parse(url, 'http');
|
// Attempt guessing the domain if the initial check failed
|
||||||
|
const domain = CoreUrl.guessMoodleDomain(url);
|
||||||
|
|
||||||
if (!urlParts || !urlParts.domain) {
|
return domain ? this.sitesProvider.checkSite(domain) : Promise.reject(error);
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.sitesProvider.checkSite(urlParts.domain);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
.then((result) => this.login(result))
|
.then((result) => this.login(result))
|
||||||
.catch((error) => this.showLoginIssue(url, error))
|
.catch((error) => this.showLoginIssue(url, error))
|
||||||
.finally(() => modal.dismiss());
|
.finally(() => modal.dismiss());
|
||||||
|
@ -177,7 +173,7 @@ export class CoreLoginSitePage {
|
||||||
*
|
*
|
||||||
* @return Promise resolved after logging in.
|
* @return Promise resolved after logging in.
|
||||||
*/
|
*/
|
||||||
private async login(response: CoreSiteCheckResponse): Promise<void> {
|
protected async login(response: CoreSiteCheckResponse): Promise<void> {
|
||||||
return this.sitesProvider.checkRequiredMinimumVersion(response.config).then(() => {
|
return this.sitesProvider.checkRequiredMinimumVersion(response.config).then(() => {
|
||||||
if (response.warning) {
|
if (response.warning) {
|
||||||
this.domUtils.showErrorModal(response.warning, true, 4000);
|
this.domUtils.showErrorModal(response.warning, true, 4000);
|
||||||
|
|
Loading…
Reference in New Issue