Skip to content

Commit bd00a1d

Browse files
committed
Get api key generation working
1 parent a7c4323 commit bd00a1d

File tree

5 files changed

+140
-84
lines changed

5 files changed

+140
-84
lines changed

example-apps/internal-knowledge-search/app-ui/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ To be able to use the index filtering and sorting in the UI you should update th
106106

107107
### Setting the environment variables
108108

109-
You need to set `REACT_APP_SEARCH_APP_NAME`, `REACT_APP_SEARCH_APP_API_KEY` and `REACT_APP_SEARCH_APP_ENDPOINT` inside [.env](.env) to the corresponding values, which you'll get when [creating a search application](https://www.elastic.co/guide/en/enterprise-search/current/search-applications.html).
109+
You need to set `REACT_APP_SEARCH_APP_NAME`, `REACT_APP_SEARCH_APP_USER`, `REACT_APP_SEARCH_APP_PASSWORD` and `REACT_APP_SEARCH_APP_ENDPOINT` inside [.env](.env) to the corresponding values, which you'll get when [creating a search application](https://www.elastic.co/guide/en/enterprise-search/current/search-applications.html).
110110

111111
### Set up DLS with SPO
112112
1. create a connector in kibana named `search-sharepoint`
@@ -117,7 +117,6 @@ You need to set `REACT_APP_SEARCH_APP_NAME`, `REACT_APP_SEARCH_APP_API_KEY` and
117117
6. define mappings, as above in this README
118118
7. create search application
119119
8. enable cors: https://www.elastic.co/guide/en/elasticsearch/reference/master/search-application-security.html#search-application-security-cors-elasticsearch
120-
9. create an API key that has read access to `.search-acl-filter-search-sharepoint` and the search application (`sharepoint`)
121120

122121

123122
### Make it go

example-apps/internal-knowledge-search/app-ui/src/components/SearchApplicationSettings.tsx

Lines changed: 133 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@ import {updateSettings} from "../store/slices/searchApplicationSettingsSlice";
44
import {useToast} from "../contexts/ToastContext";
55
import {MessageType} from "./Toast";
66
import {RootState} from "../store/store";
7-
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
8-
import {faAngleDown, faAngleUp} from "@fortawesome/free-solid-svg-icons";
9-
import {json} from "react-router-dom";
7+
108

119

1210
const SearchApplicationSettings: React.FC = () => {
1311
const dispatch = useDispatch();
14-
const {appName, appUser, appPassword, searchEndpoint, searchPersona} = useSelector((state: RootState) => state.searchApplicationSettings);
12+
const {appName, appUser, appPassword, searchEndpoint, searchPersona, searchPersonaAPIKey} = useSelector((state: RootState) => state.searchApplicationSettings);
1513
const {showToast} = useToast();
1614

1715
const fetchPersonaOptions = async () => {
@@ -29,12 +27,127 @@ const SearchApplicationSettings: React.FC = () => {
2927
}
3028
}
3129

30+
const roleName = appName+"-key-role"
31+
const defaultRoleDescriptor = {
32+
[roleName]: {
33+
"cluster": [],
34+
"indices": [
35+
{
36+
"names": [
37+
appName
38+
],
39+
"privileges": [
40+
"read"
41+
],
42+
"allow_restricted_indices": false
43+
}
44+
],
45+
"applications": [],
46+
"run_as": [],
47+
"metadata": {},
48+
"transient_metadata": {
49+
"enabled": true
50+
},
51+
"restriction": {
52+
"workflows": [
53+
"search_application_query"
54+
]
55+
}
56+
}
57+
}
58+
const personaRoleDescriptor = async () => {
59+
const identitiesIndex = ".search-acl-filter-search-sharepoint" //TODO fix hardcoded
60+
const identityPath = searchEndpoint + "/" + identitiesIndex + "/_doc/" + searchPersona
61+
const response = await fetch(identityPath, {headers: {"Authorization": "Basic " + btoa(appUser + ":" + appPassword)}});
62+
const jsonData = await response.json();
63+
console.log("Permissions lookup response is:")
64+
console.log(jsonData)
65+
const permissions = jsonData._source.query.template.params.access_control
66+
return {
67+
"dls-role": {
68+
"cluster": ["all"],
69+
"indices": [
70+
{
71+
"names": ["search-sharepoint"],// TODO: hardcoded
72+
"privileges": ["read"],
73+
"query": {
74+
"template": {
75+
"params": {
76+
"permissions": permissions
77+
},
78+
'source': `
79+
{
80+
"bool": {
81+
"filter": {
82+
"bool": {
83+
"should": [
84+
{
85+
"bool": {
86+
"must_not": {
87+
"exists": {
88+
"field": "_allow_access_control"
89+
}
90+
}
91+
}
92+
},
93+
{
94+
"terms": {
95+
"_allow_access_control": {{#toJson}}permissions{{/toJson}}
96+
}
97+
}
98+
]
99+
}
100+
}
101+
}
102+
}
103+
`
104+
}
105+
}
106+
}
107+
],
108+
"restriction": {
109+
"workflows": [
110+
"search_application_query"
111+
]
112+
}
113+
}
114+
}
115+
}
116+
const createPersonaAPIKey = async(persona) => {
117+
const roleDescriptor = persona == "admin" ? defaultRoleDescriptor : await personaRoleDescriptor()
118+
const apiKeyPath = searchEndpoint + "/_security/api_key"
119+
const response = await fetch(apiKeyPath, {
120+
method: "POST",
121+
headers: {
122+
"Authorization": "Basic " + btoa(appUser + ":" + appPassword),
123+
"Content-Type": "application/json",
124+
},
125+
body: JSON.stringify({
126+
"name": appName+"-internal-knowledge-search-example-"+persona,
127+
"expiration": "1h",
128+
"role_descriptors": roleDescriptor,
129+
"metadata": {
130+
"application": appName,
131+
"createdBy": appUser
132+
}
133+
})
134+
})
135+
const jsonData = await response.json()
136+
console.log("API key create response is:")
137+
console.log(jsonData)
138+
return jsonData.encoded
139+
}
140+
32141
const [searchPersonaOptions, setSearchPersonaOptions] = useState(["admin"]);
33142

34143
useEffect(()=>{
35144
(async()=>{
36-
const fetched = await fetchPersonaOptions()
37-
setSearchPersonaOptions(fetched)
145+
const fetchedPersonas = await fetchPersonaOptions()
146+
setSearchPersonaOptions(fetchedPersonas)
147+
if (searchPersonaAPIKey == "missing") {
148+
const createdAPIKey = await createPersonaAPIKey(searchPersona)
149+
updateSearchPersonaAPIKey(createdAPIKey)
150+
}
38151
})()
39152
},[])
40153

@@ -44,24 +157,26 @@ const SearchApplicationSettings: React.FC = () => {
44157
setIsOpen(!isOpen);
45158
};
46159

47-
const handlePersonaChange = (value: string) => {
48-
updateSearchPersona(value);
160+
const handlePersonaChange = async (value: string) => {
161+
updateSearchPersona(value, await createPersonaAPIKey(value));
49162
};
50163

51164
const handleSave = () => {
52-
dispatch(updateSettings({appName, appUser, appPassword, searchEndpoint, searchPersona}));
165+
dispatch(updateSettings({appName, appUser, appPassword, searchEndpoint, searchPersona, searchPersonaAPIKey}));
53166
showToast("Settings saved!", MessageType.Info);
54167
};
55168

56-
const updateAppName = (e) => dispatch(updateSettings({appName: e.target.value, appUser, appPassword, searchEndpoint, searchPersona}))
169+
const updateAppName = (e) => dispatch(updateSettings({appName: e.target.value, appUser, appPassword, searchEndpoint, searchPersona, searchPersonaAPIKey}))
170+
171+
const updateAppUser = (e) => dispatch(updateSettings({appName, appUser: e.target.value, appPassword, searchEndpoint, searchPersona, searchPersonaAPIKey}))
57172

58-
const updateAppUser = (e) => dispatch(updateSettings({appName, appUser: e.target.value, appPassword, searchEndpoint, searchPersona}))
173+
const updateAppPassword = (e) => dispatch(updateSettings({appName, appUser, appPassword: e.target.value, searchEndpoint, searchPersona, searchPersonaAPIKey}))
59174

60-
const updateAppPassword = (e) => dispatch(updateSettings({appName, appUser, appPassword: e.target.value, searchEndpoint, searchPersona}))
175+
const updateSearchEndpoint = (e) => dispatch(updateSettings({appName, appUser, appPassword, searchEndpoint: e.target.value, searchPersona, searchPersonaAPIKey}))
61176

62-
const updateSearchEndpoint = (e) => dispatch(updateSettings({appName, appUser, appPassword, searchEndpoint: e.target.value, searchPersona}))
177+
const updateSearchPersona = (persona, apiKey) => dispatch(updateSettings({appName, appUser, appPassword, searchEndpoint, searchPersona: persona, searchPersonaAPIKey: apiKey}))
63178

64-
const updateSearchPersona = (e) => dispatch(updateSettings({appName, appUser, appPassword, searchEndpoint, searchPersona: e}))
179+
const updateSearchPersonaAPIKey = (apiKey) => dispatch(updateSettings({appName, appUser, appPassword, searchEndpoint, searchPersona, searchPersonaAPIKey: apiKey}))
65180

66181
return (
67182
<div className="container mx-auto p-4 bg-white rounded shadow-md">
@@ -154,10 +269,13 @@ const SearchApplicationSettings: React.FC = () => {
154269
<p className="text-xs mb-2 text-gray-500">The persona on whose behalf searches will be executed</p>
155270
<div className="relative">
156271
<select
157-
onChange={(event) => handlePersonaChange(event.target.value)}
272+
onChange={ async (event) => await handlePersonaChange(event.target.value)}
158273
value={searchPersona}
159274
className="flex items-center space-x-2 p-2 bg-white rounded border border-gray-300 focus:outline-none focus:border-blue-500"
160275
>
276+
{searchPersonaOptions.includes(searchPersona) ? "" : <option value={searchPersona} key={searchPersona} className="block text-left p-2 hover:bg-gray-100 cursor-pointer">
277+
{searchPersona}
278+
</option>}
161279
{searchPersonaOptions.map((option, index) => (
162280
<option value={option} key={option} className="block text-left p-2 hover:bg-gray-100 cursor-pointer">
163281
{option}

example-apps/internal-knowledge-search/app-ui/src/models/SearchApplicationSettingsModel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ export interface SearchApplicationSettingsModel {
77
searchEndpoint: string;
88

99
searchPersona: string;
10+
11+
searchPersonaAPIKey: string;
1012
}

example-apps/internal-knowledge-search/app-ui/src/pages/SearchPage.tsx

Lines changed: 2 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export default function SearchPage() {
3737
//TODO: simplify query state and move it to one place
3838
const [query, setQuery] = useState<string>("");
3939

40-
const {appName, appUser, appPassword, searchEndpoint, searchPersona} = useSelector((state: RootState) => state.searchApplicationSettings);
40+
const {appName, appUser, appPassword, searchEndpoint, searchPersonaAPIKey} = useSelector((state: RootState) => state.searchApplicationSettings);
4141
const {sorts} = useSelector((state: RootState) => state.sort);
4242
const indexFilter = useSelector((state: RootState) => state.filter)["filters"]["Data sources"].values;
4343

@@ -47,75 +47,11 @@ export default function SearchPage() {
4747
fetchData();
4848
}, [indexFilter, sorts, appName, appUser, appPassword, searchEndpoint]);
4949

50-
const getOrCreateApiKey = async () => {
51-
if (searchPersona == "admin"){
52-
return "admin key" //TODO fix hardcoded
53-
}
54-
else {
55-
const identitiesIndex = ".search-acl-filter-search-sharepoint" //TODO fix hardcoded
56-
const identityPath = searchEndpoint + "/" + identitiesIndex + "/_doc/" + searchPersona
57-
const response = await fetch(identityPath, {headers: {"Authorization": "Basic " + btoa(appUser + ":" + appPassword)}});
58-
const jsonData = await response.json();
59-
console.log(jsonData)
60-
const permissions = jsonData._source.query.template.params.access_control
61-
const apiKeyRoleDescriptor = {
62-
name: searchPersona,
63-
role_descriptors: {
64-
"dls-role": {
65-
"cluster": ["all"],
66-
"indices": [
67-
{
68-
"names": ["search-sharepoint"],// TODO: hardcoded
69-
"privileges": ["read"],
70-
"query": {
71-
"template": {
72-
"params": {
73-
"permissions": permissions
74-
},
75-
'source': `
76-
{
77-
"bool": {
78-
"filter": {
79-
"bool": {
80-
"should": [
81-
{
82-
"bool": {
83-
"must_not": {
84-
"exists": {
85-
"field": "_allow_access_control"
86-
}
87-
}
88-
}
89-
},
90-
{
91-
"terms": {
92-
"_allow_access_control": {{#toJson}}permissions{{/toJson}}
93-
}
94-
}
95-
]
96-
}
97-
}
98-
}
99-
}
100-
`
101-
}
102-
}
103-
}
104-
]
105-
}
106-
}
107-
}
108-
109-
// TODO Use fetch: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
110-
return "missing"
111-
}
112-
}
113-
11450
const handleSearchSubmit = async () => {
11551
try {
11652
setLoading(true);
11753

118-
const apiKey = await getOrCreateApiKey()
54+
const apiKey = searchPersonaAPIKey
11955
showToast("API key is: "+apiKey)
12056

12157
const client = SearchApplicationClient(appName, searchEndpoint, apiKey, {

example-apps/internal-knowledge-search/app-ui/src/store/slices/searchApplicationSettingsSlice.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const initialState: SearchApplicationSettingsModel = {
88
appUser: process.env.REACT_APP_SEARCH_USER || "elastic",
99
appPassword: process.env.REACT_APP_SEARCH_PASSWORD || "changeme",
1010
searchEndpoint: process.env.REACT_APP_SEARCH_APP_ENDPOINT || "https://some-search-end-point.co",
11-
searchPersona: "admin"
11+
searchPersona: "admin",
12+
searchPersonaAPIKey: "missing"
1213
};
1314

1415
const searchApplicationSettingsSlice = createSlice({

0 commit comments

Comments
 (0)