Page MenuHomeSoftware Heritage

D7843.id28340.diff
No OneTemporary

D7843.id28340.diff

diff --git a/assets/src/bundles/save/index.js b/assets/src/bundles/save/index.js
--- a/assets/src/bundles/save/index.js
+++ b/assets/src/bundles/save/index.js
@@ -372,11 +372,16 @@
validUrl = isGitRepoUrl(originUrl);
}
+ let customValidity = '';
if (validUrl) {
- input.setCustomValidity('');
+ if ((originUrl.password !== '' && originUrl.password !== 'anonymous')) {
+ customValidity = 'The origin url contains a password and cannot be accepted for security reasons';
+ }
} else {
- input.setCustomValidity('The origin url is not valid or does not reference a code repository');
+ customValidity = 'The origin url is not valid or does not reference a code repository';
}
+ input.setCustomValidity(customValidity);
+ $(input).siblings('.invalid-feedback').text(customValidity);
}
export function initTakeNewSnapshot() {
diff --git a/cypress/integration/origin-save.spec.js b/cypress/integration/origin-save.spec.js
--- a/cypress/integration/origin-save.spec.js
+++ b/cypress/integration/origin-save.spec.js
@@ -777,4 +777,73 @@
.should('have.class', 'active');
});
+ it('should not accept origin URL with password', function() {
+
+ makeOriginSaveRequest('git', 'https://user:password@git.example.org/user/repo');
+
+ cy.get('.invalid-feedback')
+ .should('contain', 'The origin url contains a password and cannot be accepted for security reasons');
+
+ });
+
+ it('should accept origin URL with username but without password', function() {
+
+ cy.adminLogin();
+ cy.visit(url);
+
+ const originUrl = 'https://user@git.example.org/user/repo';
+
+ stubSaveRequest({requestUrl: this.Urls.api_1_save_origin('git', originUrl),
+ saveRequestStatus: 'accepted',
+ originUrl: originUrl,
+ saveTaskStatus: 'not yet scheduled'});
+
+ makeOriginSaveRequest('git', originUrl);
+
+ cy.wait('@saveRequest').then(() => {
+ checkAlertVisible('success', saveCodeMsg['success']);
+ });
+
+ });
+
+ it('should accept origin URL with anonymous credentials', function() {
+
+ cy.adminLogin();
+ cy.visit(url);
+
+ const originUrl = 'https://anonymous:anonymous@git.example.org/user/repo';
+
+ stubSaveRequest({requestUrl: this.Urls.api_1_save_origin('git', originUrl),
+ saveRequestStatus: 'accepted',
+ originUrl: originUrl,
+ saveTaskStatus: 'not yet scheduled'});
+
+ makeOriginSaveRequest('git', originUrl);
+
+ cy.wait('@saveRequest').then(() => {
+ checkAlertVisible('success', saveCodeMsg['success']);
+ });
+
+ });
+
+ it('should accept origin URL with empty password', function() {
+
+ cy.adminLogin();
+ cy.visit(url);
+
+ const originUrl = 'https://anonymous:@git.example.org/user/repo';
+
+ stubSaveRequest({requestUrl: this.Urls.api_1_save_origin('git', originUrl),
+ saveRequestStatus: 'accepted',
+ originUrl: originUrl,
+ saveTaskStatus: 'not yet scheduled'});
+
+ makeOriginSaveRequest('git', originUrl);
+
+ cy.wait('@saveRequest').then(() => {
+ checkAlertVisible('success', saveCodeMsg['success']);
+ });
+
+ });
+
});
diff --git a/swh/web/common/origin_save.py b/swh/web/common/origin_save.py
--- a/swh/web/common/origin_save.py
+++ b/swh/web/common/origin_save.py
@@ -9,6 +9,7 @@
import json
import logging
from typing import Any, Dict, List, Optional, Tuple
+from urllib.parse import urlparse
from prometheus_client import Gauge
import requests
@@ -218,7 +219,14 @@
_validate_url(origin_url)
except ValidationError:
raise BadInputExc(
- "The provided origin url (%s) is not valid!" % escape(origin_url)
+ f"The provided origin url ({escape(origin_url)}) is not valid!"
+ )
+
+ parsed_url = urlparse(origin_url)
+ if parsed_url.password not in (None, "", "anonymous"):
+ raise BadInputExc(
+ "The provided origin url contains a password and cannot be "
+ "accepted for security reasons."
)
diff --git a/swh/web/tests/api/views/test_origin_save.py b/swh/web/tests/api/views/test_origin_save.py
--- a/swh/web/tests/api/views/test_origin_save.py
+++ b/swh/web/tests/api/views/test_origin_save.py
@@ -603,3 +603,55 @@
assert SaveOriginRequest.objects.get(user_ids__contains=f'"{regular_user.id}"')
assert SaveOriginRequest.objects.get(user_ids__contains=f'"{regular_user2.id}"')
+
+
+def test_reject_origin_url_with_password(api_client, swh_scheduler):
+ url = reverse(
+ "api-1-save-origin",
+ url_args={
+ "visit_type": "git",
+ "origin_url": "https://user:password@git.example.org/user/repo",
+ },
+ )
+ resp = check_api_post_responses(api_client, url, status_code=400)
+
+ assert resp.data == {
+ "exception": "BadInputExc",
+ "reason": (
+ "The provided origin url contains a password and cannot "
+ "be accepted for security reasons."
+ ),
+ }
+
+
+def test_accept_origin_url_with_username_but_without_password(
+ api_client, swh_scheduler
+):
+ url = reverse(
+ "api-1-save-origin",
+ url_args={
+ "visit_type": "git",
+ "origin_url": "https://user@git.example.org/user/repo",
+ },
+ )
+ check_api_post_responses(api_client, url, status_code=200)
+
+
+@pytest.mark.parametrize(
+ "origin_url",
+ [
+ "https://anonymous:anonymous@git.example.org/user/repo",
+ "https://anonymous:@git.example.org/user/repo",
+ ],
+)
+def test_accept_origin_url_with_anonymous_credentials(
+ api_client, swh_scheduler, origin_url
+):
+ url = reverse(
+ "api-1-save-origin",
+ url_args={
+ "visit_type": "git",
+ "origin_url": origin_url,
+ },
+ )
+ check_api_post_responses(api_client, url, status_code=200)

File Metadata

Mime Type
text/plain
Expires
Sun, Aug 24, 6:04 PM (3 d, 9 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3223307

Event Timeline