From 37afe3d29939c63a5f47c8128e05b2f0fd305efb Mon Sep 17 00:00:00 2001
From: John Labbate <90406009+john-labbate@users.noreply.github.com>
Date: Fri, 6 Sep 2024 09:02:33 -0400
Subject: [PATCH] Coommit includes the following: (#634)
- Titles of FASCA and Drone in all Trainings #616
- User Profile > Save Changes Without Returning the User to the Search Results Screen #621
- Bug: Admin User unable to download another user's certificate #623
- Dependabot Alert: Server-Side Request Forgery in axios #620
---
.gitignore | 1 +
.../src/components/AdminEditReporting.vue | 18 ++-
.../src/components/AdminEditUserDetails.vue | 66 +++--------
.../src/components/AdminRepository.vue | 106 ++++++++++++++++++
.../src/components/AdminSearchUser.vue | 76 ++++---------
.../src/components/RepositoryFactory.vue | 11 ++
.../__tests__/AdminEditReporting.spec.js | 4 +-
.../__tests__/AdminSearchUser.spec.js | 6 +-
.../content/training_fleet_pc/lesson03.mdx | 6 +-
.../content/training_purchase/lesson05.mdx | 6 +-
.../content/training_purchase_pc/lesson03.mdx | 6 +-
.../src/content/training_travel/lesson03.mdx | 6 +-
.../content/training_travel_pc/lesson03.mdx | 6 +-
training/api/api_v1/certificates.py | 21 +++-
training/api/api_v1/users.py | 16 ++-
training/tests/test_api_certificates.py | 63 ++++++++++-
training/tests/test_api_users.py | 12 ++
17 files changed, 299 insertions(+), 131 deletions(-)
create mode 100644 training-front-end/src/components/AdminRepository.vue
create mode 100644 training-front-end/src/components/RepositoryFactory.vue
diff --git a/.gitignore b/.gitignore
index c6869d21..4e546d45 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ __pycache__
_site/
node_modules
.coverage
+.idea/workspace.xml
diff --git a/training-front-end/src/components/AdminEditReporting.vue b/training-front-end/src/components/AdminEditReporting.vue
index f487010f..c0f581ce 100644
--- a/training-front-end/src/components/AdminEditReporting.vue
+++ b/training-front-end/src/components/AdminEditReporting.vue
@@ -20,7 +20,19 @@ const editing = ref(false)
// Copy to avoid modifying parent prop and allow cancelling edits
const agencies = ref([...props.user.report_agencies])
-const emit = defineEmits(['cancel', 'save', 'userUpdateSuccess'])
+watch(
+ () => props.user,
+ (updatedUser) => {
+ if (updatedUser && updatedUser.report_agencies) {
+ agencies.value = [...updatedUser.report_agencies];
+ } else {
+ agencies.value = [];
+ }
+ },
+ { deep: true } // Ensures that nested changes within the user object are detected
+);
+
+const emit = defineEmits(['cancel', 'updateReportingAccess', 'userUpdateSuccess'])
const agency_options = useStore(agencyList)
const bureaus = useStore(bureauList)
@@ -41,7 +53,7 @@ function editUserAgencies(e, checked) {
})
} else {
agencies.value = agencies.value.filter(agency => agency.id != e.id)
- emit('save', props.user.id, agencies.value)
+ emit('updateReportingAccess', props.user.id, agencies.value)
}
}
@@ -268,7 +280,7 @@ function formatDate(dateStr) {
diff --git a/training-front-end/src/components/AdminEditUserDetails.vue b/training-front-end/src/components/AdminEditUserDetails.vue
index 62cb3aa5..35353325 100644
--- a/training-front-end/src/components/AdminEditUserDetails.vue
+++ b/training-front-end/src/components/AdminEditUserDetails.vue
@@ -7,9 +7,10 @@ import {useVuelidate} from "@vuelidate/core";
import ValidatedSelect from "./ValidatedSelect.vue";
import {useStore} from "@nanostores/vue";
import {agencyList, bureauList, setSelectedAgencyId} from "../stores/agencies.js";
-import {profile} from "../stores/user.js";
import USWDSAlert from "./USWDSAlert.vue";
import SpinnerGraphic from "./SpinnerGraphic.vue";
+import { RepositoryFactory } from "./RepositoryFactory.vue";
+const adminRepository = RepositoryFactory.get('admin')
const props = defineProps({
userToEdit: {
@@ -17,8 +18,7 @@ const props = defineProps({
required: true,
}
})
-const user = useStore(profile)
-const base_url = import.meta.env.PUBLIC_API_BASE_URL
+
const {withMessage} = helpers
const agency_options = useStore(agencyList)
const bureaus = useStore(bureauList)
@@ -62,8 +62,7 @@ onBeforeMount(async () => {
})
async function update_user_info() {
- error.value = ref()
- show_error.value = false
+ clearErrors()
const isFormValid = await v_all_info$.value.$validate()
if (!isFormValid) {
@@ -72,60 +71,29 @@ async function update_user_info() {
is_saving.value = true
show_spinner.value = true
- // When user has choosen a bureau use that id instead of the agency
+ // When user has chosen a bureau use that id instead of the agency
let {bureau_id, ...user_data} = user_input
if (bureau_id) {
user_data.agency_id = bureau_id
}
- const apiURL = new URL(`${base_url}/api/v1/users/${props.userToEdit.id}`)
- let response = ref();
- try {
- response.value = await fetch(apiURL, {
- method: 'PATCH',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${user.value.jwt}`
- },
- body: JSON.stringify(user_data)
- })
- } catch (error) {
- setError({
- name: 'Server Error',
- message: 'Sorry, we had an error connecting to the server.'
- })
+ try{
+ let updatedUser = await adminRepository.updateUser(props.userToEdit.id, user_data)
is_saving.value = false
show_spinner.value = false
- return
- }
- if (!response.value.ok) {
- is_saving.value = false
- show_spinner.value = false
- if (response.value.status === 400) {
- setError({
- name: 'Unauthorized',
- message: 'You are not authorized to edit profile.'
- })
- return
- }
- if (response.value.status === 403) {
- setError({
- name: 'Unauthorized',
- message: "You can not update your own profile."
- })
- return
- }
+ let successMessage = `Successfully updated ${updatedUser.email}`
+ emit('completeUserUpdate', successMessage)
+ } catch(err){
setError({
- name: 'Error',
- message: "Error contacting server."
+ name: 'Response Error',
+ message: err
})
- return
}
- is_saving.value = false
- show_spinner.value = false
- let updatedUser = await response.value.json()
- let successMessage = `Successfully updated ${updatedUser.email}`
- emit('completeUserUpdate', successMessage)
+}
+
+function clearErrors(){
+ error.value = ref()
+ show_error.value = false
}
function setError(event) {
diff --git a/training-front-end/src/components/AdminRepository.vue b/training-front-end/src/components/AdminRepository.vue
new file mode 100644
index 00000000..dac871ef
--- /dev/null
+++ b/training-front-end/src/components/AdminRepository.vue
@@ -0,0 +1,106 @@
+
\ No newline at end of file
diff --git a/training-front-end/src/components/AdminSearchUser.vue b/training-front-end/src/components/AdminSearchUser.vue
index 3e42328d..3c1a8afa 100644
--- a/training-front-end/src/components/AdminSearchUser.vue
+++ b/training-front-end/src/components/AdminSearchUser.vue
@@ -5,17 +5,11 @@
import USWDSPagination from "./USWDSPagination.vue";
import USWDSAlert from './USWDSAlert.vue'
import { setSelectedAgencyId} from '../stores/agencies'
- import { useStore } from '@nanostores/vue'
- import { profile} from '../stores/user'
-
- const user = useStore(profile)
+ import { RepositoryFactory } from "./RepositoryFactory.vue";
+ const adminRepository = RepositoryFactory.get('admin')
const PAGE_SIZE = 25
- const base_url = import.meta.env.PUBLIC_API_BASE_URL
- const report_url = `${base_url}/api/v1/users/`
- const update_url = `${base_url}/api/v1/users/edit-user-for-reporting/`
-
let currentPage = ref(0)
let numberOfResults = ref(0)
const numberOfPages = computed(() => Math.ceil(numberOfResults.value/PAGE_SIZE))
@@ -36,23 +30,8 @@
async function search() {
clearAlerts()
noResults.value = false
- const url = new URL(`${report_url}`)
- url.search = new URLSearchParams({searchText: searchTerm.value, page_number: currentPage.value + 1})
-
try {
- const response = await fetch(
- url, {
- method: 'GET',
- headers: {
- 'Authorization': `Bearer ${user.value.jwt}`
- }
- }
- )
- if (! response.ok) {
- const message = await response.text()
- throw new Error(message)
- }
- let search_results = await response.json()
+ let search_results = await adminRepository.userSearch(searchTerm.value, currentPage.value)
searchResults.value = search_results.users
numberOfResults.value = search_results.total_count
noResults.value = search_results.total_count === 0
@@ -62,50 +41,41 @@
}
async function updateUserReports(userId, agencyIds) {
- const agencies = agencyIds.map(a => a.id)
- const url = new URL(update_url)
- url.search = new URLSearchParams({user_id: userId})
try {
- const response = await fetch(
- url, {
- method: "PATCH",
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${user.value.jwt}`
- },
- body: JSON.stringify(agencies)
- }
- )
- if (!response.ok) {
- const message = await response.text()
- throw new Error(message)
- }
- let updatedUser = await response.json()
+ let updatedUser = await adminRepository.updateUserReports(userId, agencyIds)
selectedUser.value.report_agencies = updatedUser.report_agencies
- setCurrentUser(undefined)
- setSelectedAgencyId(undefined)
+ updateUserSuccess("Updated users reporting access")
+ refreshSelectedUser()
} catch (err){
error.value = err
}
}
- function setCurrentUser(e) {
+ async function refreshSelectedUser(){
+ if(!selectedUser.value){
+ return
+ }
+
+ try{
+ selectedUser.value = await adminRepository.getUser(selectedUser.value.id)
+ } catch (err) {
+ error.value = err
+ }
+ }
+
+ function setSelectedUser(e) {
selectedUser.value = e
}
function cancelEdit(){
- setCurrentUser(undefined)
+ setSelectedUser(undefined)
setSelectedAgencyId(undefined)
}
async function updateUserSuccess(message) {
+ refreshSelectedUser()
successMessage.value = message
showSuccessMessage.value = true
- cancelEdit()
- currentPage.value = 0
- numberOfResults.value = 0
- searchTerm.value = ''
- searchResults.value = []
}
function clearAlerts() {
@@ -182,7 +152,7 @@
diff --git a/training-front-end/src/components/RepositoryFactory.vue b/training-front-end/src/components/RepositoryFactory.vue
new file mode 100644
index 00000000..91ca9812
--- /dev/null
+++ b/training-front-end/src/components/RepositoryFactory.vue
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/training-front-end/src/components/__tests__/AdminEditReporting.spec.js b/training-front-end/src/components/__tests__/AdminEditReporting.spec.js
index 71062212..4641a8ed 100644
--- a/training-front-end/src/components/__tests__/AdminEditReporting.spec.js
+++ b/training-front-end/src/components/__tests__/AdminEditReporting.spec.js
@@ -80,8 +80,8 @@ describe('AdminAgencySelect', async () => {
const update_button = wrapper.find('button[id="update-user"]')
update_button.trigger('click')
- expect(wrapper.emitted()['save'][0][0]).toEqual(users[0].id)
- expect(wrapper.emitted()['save'][0][1]).toEqual([users[0].report_agencies[1]])
+ expect(wrapper.emitted()['updateReportingAccess'][0][0]).toEqual(users[0].id)
+ expect(wrapper.emitted()['updateReportingAccess'][0][1]).toEqual([users[0].report_agencies[1]])
})
it("emits cancel when cancel button is clicked", async () => {
diff --git a/training-front-end/src/components/__tests__/AdminSearchUser.spec.js b/training-front-end/src/components/__tests__/AdminSearchUser.spec.js
index 997dc490..381cadf7 100644
--- a/training-front-end/src/components/__tests__/AdminSearchUser.spec.js
+++ b/training-front-end/src/components/__tests__/AdminSearchUser.spec.js
@@ -65,7 +65,7 @@ describe('AdminAgencySelect', async () => {
const updateFetchSpy = vi.spyOn(global, 'fetch').mockImplementation(() => {
return Promise.resolve({ok: true, status:200, json: () => Promise.resolve(users[0])})
})
- await adminReporting.vm.$emit('save', "1", [{id: 10}])
+ await adminReporting.vm.$emit('updateReportingAccess', "1", [{id: 10}])
expect(updateFetchSpy).nthCalledWith(1, expect.any(URL), {
body: '[10]',
@@ -75,6 +75,8 @@ describe('AdminAgencySelect', async () => {
'Content-Type': 'application/json'
},
})
+ //Ensure it stayed on User Profile
+ expect(wrapper.text()).toContain('User Profile');
})
it('allows user to cancel update', async () => {
@@ -191,7 +193,7 @@ describe('AdminAgencySelect', async () => {
vi.spyOn(global, 'fetch').mockImplementation(() => {
return Promise.resolve({ok: false, status:403, text: () => Promise.resolve('Office of Unspecified Services is not a real agency')})
})
- await adminReporting.vm.$emit('save', "1", [{id: 10}])
+ await adminReporting.vm.$emit('updateReportingAccess', "1", [{id: 10}])
await flushPromises()
const alert = await wrapper.find('[data-test="alert-container"]')
diff --git a/training-front-end/src/content/training_fleet_pc/lesson03.mdx b/training-front-end/src/content/training_fleet_pc/lesson03.mdx
index 1f235f12..60efaa41 100644
--- a/training-front-end/src/content/training_fleet_pc/lesson03.mdx
+++ b/training-front-end/src/content/training_fleet_pc/lesson03.mdx
@@ -85,12 +85,12 @@ Strategic payment solutions offered under the [GSA SmartPay 3 Master Contract](h
## What is Section 889 and how does it apply to purchases?
Section 889 of the John S. McCain National Defense Authorization Act (NDAA) for Fiscal Year 2019 ([P.L. 115-232 [PDF, 789 pages]](https://www.congress.gov/115/plaws/publ232/PLAW-115publ232.pdf)) and the [Federal Acquisition Regulation (FAR) Case 2018-017](https://www.federalregister.gov/documents/2019/08/13/2019-17201/federal-acquisition-regulation-prohibition-on-contracting-for-certain-telecommunications-and-video) prohibit the purchase of covered telecommunications equipment and services from merchants who sell products containing spyware. These devices could pose a threat to U.S. security by spying on or disrupting communications within the U.S. Therefore, purchase card/account holders should follow their agency’s policy regarding Section 889 compliance.
-### FASCSA Order Check
+## What is the FASCSA Order Check?
Before making any purchase, buyers should review Federal Acquisition Supply Chain Security Act (FASCSA) orders.
The federal government issued an [interim rule](https://www.federalregister.gov/documents/2023/10/05/2023-21320/federal-acquisition-regulation-implementation-of-federal-acquisition-supply-chain-security-act) that amends the Federal Acquisition Regulation (FAR) to implement supply chain risk information sharing and FASCSA orders. This rule became effective on December 4, 2023.
-#### Accessing FASCSA Orders
+### Accessing FASCSA Orders
[The System for Award Management (SAM)](https://sam.gov/) is an official website of the U.S. Government that helps users navigate the federal award lifecycle.
The site stores FASCSA order data entered by the Department of Homeland Security (DHS), the Department of Defense (DoD), and the Director of National Intelligence (DNI).
@@ -103,7 +103,7 @@ Buyers should:
- Keep in mind that the FASCSA order review should take place for all purchases at any dollar threshold.
- Note that until DHS, DoD, or DNI create the first FASCSA order in [SAM.gov](https://sam.gov/), the downloaded file will be empty.
-### American Security Drone Act Of 2023
+## What is the American Security Drone Act Of 2023?
On December 22, 2023, the President signed the [National Defense Authorization Act for Fiscal Year 2024 (NDAA)](https://www.congress.gov/bill/118th-congress/house-bill/2670).
As part of the NDAA, Sections 1821 and 1826 contain prohibitions on using the GSA SmartPay purchase card to buy any covered unmanned aircraft systems from covered foreign entities.
diff --git a/training-front-end/src/content/training_purchase/lesson05.mdx b/training-front-end/src/content/training_purchase/lesson05.mdx
index a0dbe26b..88031fde 100644
--- a/training-front-end/src/content/training_purchase/lesson05.mdx
+++ b/training-front-end/src/content/training_purchase/lesson05.mdx
@@ -86,12 +86,12 @@ POS is the point where a transaction is finalized or the moment where a customer
Section 889 of the John S. McCain National Defense Authorization Act (NDAA) for Fiscal Year 2019 ([P.L. 115-232 [PDF, 789 pages]](https://www.congress.gov/115/plaws/publ232/PLAW-115publ232.pdf)) and the[ Federal Acquisition Regulation (FAR) Case 2018-017](https://www.federalregister.gov/documents/2019/08/13/2019-17201/federal-acquisition-regulation-prohibition-on-contracting-for-certain-telecommunications-and-video) prohibit the purchase of covered telecommunications equipment and services from vendors who sell products containing spyware. These devices could pose a threat to U.S. security by spying on or disrupting communications within the U.S. Therefore, purchase card/account holders should follow their agency’s policy regarding Section 889 compliance.
-### FASCSA Order Check
+## What is the FASCSA Order Check?
Before making any purchase, buyers should review Federal Acquisition Supply Chain Security Act (FASCSA) orders.
The federal government issued an [interim rule](https://www.federalregister.gov/documents/2023/10/05/2023-21320/federal-acquisition-regulation-implementation-of-federal-acquisition-supply-chain-security-act) that amends the Federal Acquisition Regulation (FAR) to implement supply chain risk information sharing and FASCSA orders. This rule became effective on December 4, 2023.
-#### Accessing FASCSA Orders
+### Accessing FASCSA Orders
[The System for Award Management (SAM)](https://sam.gov/) is an official website of the U.S. Government that helps users navigate the federal award lifecycle.
The site stores FASCSA order data entered by the Department of Homeland Security (DHS), the Department of Defense (DoD), and the Director of National Intelligence (DNI).
@@ -104,7 +104,7 @@ Buyers should:
- Keep in mind that the FASCSA order review should take place for all purchases at any dollar threshold.
- Note that until DHS, DoD, or DNI create the first FASCSA order in [SAM.gov](https://sam.gov/), the downloaded file will be empty.
-### American Security Drone Act Of 2023
+## What is the American Security Drone Act Of 2023?
On December 22, 2023, the President signed the [National Defense Authorization Act for Fiscal Year 2024 (NDAA)](https://www.congress.gov/bill/118th-congress/house-bill/2670).
As part of the NDAA, Sections 1821 and 1826 contain prohibitions on using the GSA SmartPay purchase card to buy any covered unmanned aircraft systems from covered foreign entities.
diff --git a/training-front-end/src/content/training_purchase_pc/lesson03.mdx b/training-front-end/src/content/training_purchase_pc/lesson03.mdx
index 447a1f0e..04dd4ae1 100644
--- a/training-front-end/src/content/training_purchase_pc/lesson03.mdx
+++ b/training-front-end/src/content/training_purchase_pc/lesson03.mdx
@@ -78,12 +78,12 @@ Strategic payment solutions offered under the [GSA SmartPay 3 Master Contract](h
## What is Section 889 and how does it apply to purchases?
Section 889 of the John S. McCain National Defense Authorization Act (NDAA) for Fiscal Year 2019 ([P.L. 115-232 [PDF, 789 pages]](https://www.congress.gov/115/plaws/publ232/PLAW-115publ232.pdf)) and the [Federal Acquisition Regulation (FAR) Case 2018-017](https://www.federalregister.gov/documents/2019/08/13/2019-17201/federal-acquisition-regulation-prohibition-on-contracting-for-certain-telecommunications-and-video) prohibit the purchase of covered telecommunications equipment and services from merchants who sell products containing spyware. These devices could pose a threat to U.S. security by spying on or disrupting communications within the U.S. Therefore, purchase card/account holders should follow their agency’s policy regarding Section 889 compliance.
-### FASCSA Order Check
+## What is the FASCSA Order Check?
Before making any purchase, buyers should review Federal Acquisition Supply Chain Security Act (FASCSA) orders.
The federal government issued an [interim rule](https://www.federalregister.gov/documents/2023/10/05/2023-21320/federal-acquisition-regulation-implementation-of-federal-acquisition-supply-chain-security-act) that amends the Federal Acquisition Regulation (FAR) to implement supply chain risk information sharing and FASCSA orders. This rule became effective on December 4, 2023.
-#### Accessing FASCSA Orders
+### Accessing FASCSA Orders
[The System for Award Management (SAM)](https://sam.gov/) is an official website of the U.S. Government that helps users navigate the federal award lifecycle.
The site stores FASCSA order data entered by the Department of Homeland Security (DHS), the Department of Defense (DoD), and the Director of National Intelligence (DNI).
@@ -96,7 +96,7 @@ Buyers should:
- Keep in mind that the FASCSA order review should take place for all purchases at any dollar threshold.
- Note that until DHS, DoD, or DNI create the first FASCSA order in [SAM.gov](https://sam.gov/), the downloaded file will be empty.
-### American Security Drone Act Of 2023
+## What is the American Security Drone Act Of 2023?
On December 22, 2023, the President signed the [National Defense Authorization Act for Fiscal Year 2024 (NDAA)](https://www.congress.gov/bill/118th-congress/house-bill/2670).
As part of the NDAA, Sections 1821 and 1826 contain prohibitions on using the GSA SmartPay purchase card to buy any covered unmanned aircraft systems from covered foreign entities.
diff --git a/training-front-end/src/content/training_travel/lesson03.mdx b/training-front-end/src/content/training_travel/lesson03.mdx
index c5fa759d..eddc8bae 100644
--- a/training-front-end/src/content/training_travel/lesson03.mdx
+++ b/training-front-end/src/content/training_travel/lesson03.mdx
@@ -47,12 +47,12 @@ The GSA SmartPay Travel card/account may be used for authorized official travel
## What is Section 889 and how does it apply to purchases?
Section 889 of the John S. McCain National Defense Authorization Act (NDAA) for Fiscal Year 2019 ([P.L. 115-232 [PDF, 789 pages]](https://www.congress.gov/115/plaws/publ232/PLAW-115publ232.pdf)) and the [Federal Acquisition Regulation (FAR) Case 2018-017](https://www.federalregister.gov/documents/2019/08/13/2019-17201/federal-acquisition-regulation-prohibition-on-contracting-for-certain-telecommunications-and-video) prohibit the purchase of covered telecommunications equipment and services from merchants who sell products containing spyware. These devices could pose a threat to U.S. security by spying on or disrupting communications within the U.S. Therefore, GSA SmartPay card/account holders should follow their agency’s policy regarding Section 889 compliance.
-### FASCSA Order Check
+## What is the FASCSA Order Check?
Before making any purchase, buyers should review Federal Acquisition Supply Chain Security Act (FASCSA) orders.
The federal government issued an [interim rule](https://www.federalregister.gov/documents/2023/10/05/2023-21320/federal-acquisition-regulation-implementation-of-federal-acquisition-supply-chain-security-act) that amends the Federal Acquisition Regulation (FAR) to implement supply chain risk information sharing and FASCSA orders. This rule became effective on December 4, 2023.
-#### Accessing FASCSA Orders
+### Accessing FASCSA Orders
[The System for Award Management (SAM)](https://sam.gov/) is an official website of the U.S. Government that helps users navigate the federal award lifecycle.
The site stores FASCSA order data entered by the Department of Homeland Security (DHS), the Department of Defense (DoD), and the Director of National Intelligence (DNI).
@@ -65,7 +65,7 @@ Buyers should:
- Keep in mind that the FASCSA order review should take place for all purchases at any dollar threshold.
- Note that until DHS, DoD, or DNI create the first FASCSA order in [SAM.gov](https://sam.gov/), the downloaded file will be empty.
-### American Security Drone Act Of 2023
+## What is the American Security Drone Act Of 2023?
On December 22, 2023, the President signed the [National Defense Authorization Act for Fiscal Year 2024 (NDAA)](https://www.congress.gov/bill/118th-congress/house-bill/2670).
As part of the NDAA, Sections 1821 and 1826 contain prohibitions on using the GSA SmartPay purchase card to buy any covered unmanned aircraft systems from covered foreign entities.
diff --git a/training-front-end/src/content/training_travel_pc/lesson03.mdx b/training-front-end/src/content/training_travel_pc/lesson03.mdx
index 513f63c7..e9571e91 100644
--- a/training-front-end/src/content/training_travel_pc/lesson03.mdx
+++ b/training-front-end/src/content/training_travel_pc/lesson03.mdx
@@ -108,12 +108,12 @@ The GSA SmartPay Travel card/account may be used for authorized official travel
## What is Section 889 and how does it apply to purchases?
Section 889 of the John S. McCain National Defense Authorization Act (NDAA) for Fiscal Year 2019 ([P.L. 115-232 [PDF, 789 pages]](https://www.congress.gov/115/plaws/publ232/PLAW-115publ232.pdf)) and the [Federal Acquisition Regulation (FAR) Case 2018-017](https://www.federalregister.gov/documents/2019/08/13/2019-17201/federal-acquisition-regulation-prohibition-on-contracting-for-certain-telecommunications-and-video) prohibit the purchase of covered telecommunications equipment and services from merchants who sell products containing spyware. These devices could pose a threat to U.S. security by spying on or disrupting communications within the U.S. Therefore, GSA SmartPay card/account holders should follow their agency’s policy regarding Section 889 compliance.
-### FASCSA Order Check
+## What is the FASCSA Order Check?
Before making any purchase, buyers should review Federal Acquisition Supply Chain Security Act (FASCSA) orders.
The federal government issued an [interim rule](https://www.federalregister.gov/documents/2023/10/05/2023-21320/federal-acquisition-regulation-implementation-of-federal-acquisition-supply-chain-security-act) that amends the Federal Acquisition Regulation (FAR) to implement supply chain risk information sharing and FASCSA orders. This rule became effective on December 4, 2023.
-#### Accessing FASCSA Orders
+### Accessing FASCSA Orders
[The System for Award Management (SAM)](https://sam.gov/) is an official website of the U.S. Government that helps users navigate the federal award lifecycle.
The site stores FASCSA order data entered by the Department of Homeland Security (DHS), the Department of Defense (DoD), and the Director of National Intelligence (DNI).
@@ -126,7 +126,7 @@ Buyers should:
- Keep in mind that the FASCSA order review should take place for all purchases at any dollar threshold.
- Note that until DHS, DoD, or DNI create the first FASCSA order in [SAM.gov](https://sam.gov/), the downloaded file will be empty.
-### American Security Drone Act Of 2023
+## What is the American Security Drone Act Of 2023?
On December 22, 2023, the President signed the [National Defense Authorization Act for Fiscal Year 2024 (NDAA)](https://www.congress.gov/bill/118th-congress/house-bill/2670).
As part of the NDAA, Sections 1821 and 1826 contain prohibitions on using the GSA SmartPay purchase card to buy any covered unmanned aircraft systems from covered foreign entities.
diff --git a/training/api/api_v1/certificates.py b/training/api/api_v1/certificates.py
index bd479991..e1335a52 100644
--- a/training/api/api_v1/certificates.py
+++ b/training/api/api_v1/certificates.py
@@ -1,4 +1,4 @@
-from typing import List, Any
+from typing import List, Any, Dict
from fastapi import APIRouter, status, HTTPException, Depends, Response
from training.schemas import UserCertificate, CertificateType, CertificateListValue
from training.repositories import CertificateRepository
@@ -52,11 +52,13 @@ def get_certificate_by_type_and_id(
):
pdf_bytes = None
filename = ''
+ is_admin_user = is_admin(user)
+ user_id = user["id"]
if (certType == CertificateType.QUIZ.value):
db_user_certificate = certificateRepo.get_certificate_by_id(id)
- verify_certificate_is_valid(db_user_certificate, user["id"])
+ verify_certificate_is_valid(db_user_certificate, user_id, is_admin_user)
pdf_bytes = certificateService.generate_pdf(
db_user_certificate.quiz_name,
@@ -69,7 +71,7 @@ def get_certificate_by_type_and_id(
elif (certType == CertificateType.GSPC.value):
certificate = certificateRepo.get_gspc_certificate_by_id(id)
- verify_certificate_is_valid(certificate, user["id"])
+ verify_certificate_is_valid(certificate, user_id, is_admin_user)
pdf_bytes = certificateService.generate_gspc_pdf(
certificate.user_name,
@@ -87,9 +89,18 @@ def get_certificate_by_type_and_id(
return Response(pdf_bytes, headers=headers, media_type='application/pdf')
-def verify_certificate_is_valid(cert: object, user_id: int):
+def verify_certificate_is_valid(cert: object, user_id: int, is_admin_user: bool):
if cert is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
- if cert.user_id != user_id:
+ if cert.user_id != user_id and not is_admin_user:
raise HTTPException(status_code=401, detail="Not Authorized")
+
+
+def is_admin(user: Dict[str, List[str]]) -> bool:
+ # Ensure that 'roles' is in the user dictionary and is a list
+ if 'roles' not in user or not isinstance(user['roles'], list):
+ return False
+
+ # Normalize roles to avoid case sensitivity issues
+ return 'Admin' in user['roles']
diff --git a/training/api/api_v1/users.py b/training/api/api_v1/users.py
index 3460dce8..5de53971 100644
--- a/training/api/api_v1/users.py
+++ b/training/api/api_v1/users.py
@@ -80,13 +80,27 @@ def get_users(
'''
Get/users is used to search users for admin portal
currently search only support search by user name and email address, searchText is required field.
- It may have additional search criteira in future, which will require logic update.
+ It may have additional search criteria in future, which will require logic update.
page_number param is used to support UI pagination functionality.
It returns UserSearchResult object with a list of users and total_count used for UI pagination
'''
return repo.get_users(searchText, page_number)
+@router.get("/users/{user_id}", response_model=User)
+def get_user(
+ user_id: int,
+ repo: UserRepository = Depends(user_repository),
+ user=Depends(RequireRole(["Admin"]))
+):
+ '''
+ Get/user is used to refresh user after edits for admin portal
+ It returns a User object
+ '''
+
+ return repo.find_by_id(user_id)
+
+
@router.patch("/users/{user_id}", response_model=User)
def update_user_by_id(
user_id: int,
diff --git a/training/tests/test_api_certificates.py b/training/tests/test_api_certificates.py
index f8a047a5..d102d3da 100644
--- a/training/tests/test_api_certificates.py
+++ b/training/tests/test_api_certificates.py
@@ -3,13 +3,14 @@
from unittest.mock import MagicMock
-from fastapi import status
+from fastapi import status, HTTPException
from fastapi.testclient import TestClient
from training.api.deps import certificate_repository
from training.config import settings
from training.main import app
from training.schemas import UserCertificate, GspcCertificate, CertificateListValue
from training.services.certificate import Certificate
+from training.api.api_v1.certificates import verify_certificate_is_valid, is_admin
client = TestClient(app)
@@ -86,6 +87,11 @@ def goodJWT():
return jwt.encode({'id': 1}, settings.JWT_SECRET, algorithm="HS256")
+class MockCertificate:
+ def __init__(self, user_id):
+ self.user_id = user_id
+
+
class TestCertificateAPI:
def test_get_certificates_no_auth(self, fake_cert_repo):
response = client.get(
@@ -219,3 +225,58 @@ def test_get_specific_gspc_certificate_wrong_user(self, fake_cert_repo, goodJWT,
data={"jwtToken": goodJWT}
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
+
+ def test_verify_certificate_is_valid_certificate_none(self):
+ """Test when the certificate is None, should raise 404 HTTPException."""
+ with pytest.raises(HTTPException) as exc_info:
+ verify_certificate_is_valid(cert=None, user_id=1, is_admin_user=False)
+ assert exc_info.value.status_code == status.HTTP_404_NOT_FOUND
+
+ def test_verify_certificate_is_valid_user_not_authorized(self):
+ """Test when user_id does not match and is not an admin, should raise 401 HTTPException."""
+ cert = MockCertificate(user_id=2)
+ with pytest.raises(HTTPException) as exc_info:
+ verify_certificate_is_valid(cert=cert, user_id=1, is_admin_user=False)
+ assert exc_info.value.status_code == 401
+ assert exc_info.value.detail == "Not Authorized"
+
+ def test_verify_certificate_is_valid_user_authorized(self):
+ """Test when user_id matches, should not raise any exception."""
+ cert = MockCertificate(user_id=1)
+ try:
+ verify_certificate_is_valid(cert=cert, user_id=1, is_admin_user=False)
+ except HTTPException:
+ pytest.fail("HTTPException raised unexpectedly!")
+
+ def test_verify_certificate_is_valid_admin_user(self):
+ """Test when the user is an admin, should not raise any exception even if user_id does not match."""
+ cert = MockCertificate(user_id=2)
+ try:
+ verify_certificate_is_valid(cert=cert, user_id=1, is_admin_user=True)
+ except HTTPException:
+ pytest.fail("HTTPException raised unexpectedly!")
+
+ def test_is_admin_with_admin_role(self):
+ """Test when 'Admin' is in the roles list."""
+ user = {"roles": ["User", "Admin", "Editor"]}
+ assert is_admin(user) is True
+
+ def test_is_admin_without_admin_role(self):
+ """Test when 'Admin' is not in the roles list."""
+ user = {"roles": ["User", "Editor"]}
+ assert is_admin(user) is False
+
+ def test_is_admin_empty_roles(self):
+ """Test when the roles list is empty."""
+ user = {"roles": []}
+ assert is_admin(user) is False
+
+ def test_is_admin_roles_is_none(self):
+ """Test when the roles list is None."""
+ user = {"roles": None}
+ assert is_admin(user) is False
+
+ def test_is_admin_roles_key_missing(self):
+ """Test when the roles key is missing from the dictionary."""
+ user = {}
+ assert is_admin(user) is False
diff --git a/training/tests/test_api_users.py b/training/tests/test_api_users.py
index 4e07b2c6..ee031397 100644
--- a/training/tests/test_api_users.py
+++ b/training/tests/test_api_users.py
@@ -68,6 +68,18 @@ def test_get_users(goodJWT, mock_user_repo: UserRepository):
assert response.json()["users"] == [user.model_dump() for user in users]
+@patch('training.config.settings', 'JWT_SECRET', 'super_secret')
+def test_get_user(goodJWT, mock_user_repo: UserRepository):
+ user = UserSchemaFactory.build(name="test name")
+ mock_user_repo.find_by_id.return_value = user
+ response = client.get(
+ "/api/v1/users/1",
+ headers={"Authorization": f"Bearer {goodJWT}"}
+ )
+ assert response.status_code == status.HTTP_200_OK
+ assert response.json()["id"] == user.id
+
+
def test_edit_user_for_reporting(mock_user_repo: UserRepository, goodJWT: str):
user = UserSchemaFactory.build(roles=[])
mock_user_repo.create(user)