001package com.pingidentity.util; 002 003import com.unboundid.ldap.sdk.Entry; 004import org.apache.http.HttpEntity; 005import org.apache.http.HttpResponse; 006import org.apache.http.NameValuePair; 007import org.apache.http.client.HttpClient; 008import org.apache.http.client.entity.UrlEncodedFormEntity; 009import org.apache.http.client.methods.HttpGet; 010import org.apache.http.client.methods.HttpPost; 011import org.apache.http.client.utils.URIBuilder; 012import org.apache.http.impl.client.HttpClients; 013import org.apache.http.message.BasicNameValuePair; 014import org.apache.http.util.EntityUtils; 015import org.json.JSONArray; 016import org.json.JSONException; 017import org.json.JSONObject; 018 019import java.net.URI; 020import java.util.ArrayList; 021import java.util.Iterator; 022import java.util.List; 023import java.util.UUID; 024 025public class MSGraphAPI { 026 027 private static String apiVersion = "1.6"; 028 029 private String tenantId = null; 030 private String clientId = null; 031 private String clientSecret = null; 032 private String accessToken = null; 033 private String baseUrl = null; 034 035 public enum OBJECT_TYPE {USERS, GROUPS}; 036 037 private HttpClient httpclient = HttpClients.createDefault(); 038 039 // TODO Needs to figure out throttling with AzureAD GraphAPI. 040 // TODO Maybe allow for user to specify a throttle as an extension argument. 041 042 /** 043 * Get and cache an accessToken 044 * 045 * @param tenantId 046 * @param clientId 047 * @param clientSecret 048 */ 049 public MSGraphAPI(final String tenantId, 050 final String clientId, 051 final String clientSecret) { 052 this.tenantId = tenantId; 053 this.clientId = clientId; 054 this.clientSecret = clientSecret; 055 authenticate(); 056 057 baseUrl = "https://graph.windows.net/" + tenantId + "/"; 058 } 059 060 061 /** 062 * Authenticate to microsoft using the OAuth2 Client Credentials Grant Flow 063 * <p> 064 * https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-client-creds-grant-flow 065 */ 066 private void authenticate() { 067 HttpClient httpclient = HttpClients.createDefault(); 068 069 try { 070 URIBuilder builder = new URIBuilder("https://login.microsoftonline.com/" + tenantId + "/oauth2/token"); 071 // Specify values for the following required parameters 072 builder.setParameter("api-version", "1.0"); 073 URI uri = builder.build(); 074 HttpPost request = new HttpPost(uri); 075 076 // Specify required body to authenticate 077 List<NameValuePair> postParameters = new ArrayList<>(); 078 079 postParameters.add(new BasicNameValuePair("client_id", clientId)); 080 postParameters.add(new BasicNameValuePair("client_secret", clientSecret)); 081 postParameters.add(new BasicNameValuePair("grant_type", "client_credentials")); 082 postParameters.add(new BasicNameValuePair("resource", "https://graph.windows.net")); 083 084 request.setEntity(new UrlEncodedFormEntity(postParameters, "UTF-8")); 085 086 HttpResponse response = httpclient.execute(request); 087 HttpEntity entity = response.getEntity(); 088 if (entity != null) { 089 JSONObject json = new JSONObject(EntityUtils.toString(entity)); 090 091 accessToken = json.getString("access_token"); 092 093 //System.out.println(accessToken); 094 } 095 } catch (Exception e) { 096 System.out.println(e.getMessage()); 097 } 098 } 099 100 101 /** 102 * Get an entry basedon objectType and objectId 103 * 104 * @param objType 105 * @param objectId 106 * @return 107 */ 108 public Entry getEntry(final OBJECT_TYPE objType, 109 final UUID objectId) { 110 return getEntry(objType, objectId, null); 111 } 112 113 /** 114 * Get an etnry based on objectType and objectId. Also return specific attributes 115 * 116 * @param objType 117 * @param objectId 118 * @param attributeNames 119 * @return 120 */ 121 public Entry getEntry(final OBJECT_TYPE objType, 122 final UUID objectId, 123 final String attributeNames) { 124 MSGraphResult graphResult = getEntries(objType, objectId, 125 null, attributeNames); 126 127 List<Entry> entries = graphResult.entries; 128 129 if (entries != null && entries.size() == 1) { 130 return entries.get(0); 131 } else { 132 return null; 133 } 134 } 135 136 /** 137 * Get all entries for a particular objectType 138 * 139 * @param objType 140 * @return 141 */ 142 public MSGraphResult getEntries(final OBJECT_TYPE objType) { 143 return getEntries(objType, null, null, null); 144 } 145 146 /** 147 * Get entries for an objectType based on filter returning certain attributes 148 * 149 * @param objType 150 * @param filter 151 * @param attributeNames 152 * @return 153 */ 154 public MSGraphResult getEntries(final OBJECT_TYPE objType, 155 final String filter, 156 final String attributeNames) { 157 return getEntries(objType, null, filter, attributeNames); 158 } 159 160 /** 161 * Get a list of entries based on objectType and filter. Return a list of attributeNames. 162 * 163 * @param objType 164 * @param filter 165 * @param attributeNames 166 * @return 167 */ 168 private MSGraphResult getEntries(final OBJECT_TYPE objType, 169 final UUID objectId, 170 final String filter, 171 final String attributeNames) { 172 List<BasicNameValuePair> bnvp = new ArrayList<>(); 173 174 if (filter != null) { 175 bnvp.add(new BasicNameValuePair("$filter", filter)); 176 } 177 178 if (attributeNames != null) { 179 bnvp.add(new BasicNameValuePair("$select", attributeNames)); 180 } 181 182 return getEntries(objType, objectId, bnvp); 183 } 184 185 /** 186 * Get a list of entries based on objectType. 187 * 188 * @param objType 189 * @param objectId 190 * @param nvPairs 191 * @return 192 */ 193 private MSGraphResult getEntries(final OBJECT_TYPE objType, 194 final UUID objectId, 195 final List<BasicNameValuePair> nvPairs) { 196 MSGraphResult graphResult = new MSGraphResult(); 197 List<Entry> objList = new ArrayList<>(); 198 199 try { 200 String objectIdURL = ""; 201 202 if (objectId != null) { 203 objectIdURL = "/" + objectId; 204 } 205 206 // Specify values for path parameters (shown as {...}) 207 URIBuilder builder = new URIBuilder( baseUrl + 208 objType.toString().toLowerCase() + objectIdURL); 209 210 List<NameValuePair> parameters = new ArrayList<>(); 211 212 if (nvPairs != null) { 213 for (NameValuePair nvp : nvPairs) { 214 parameters.add(nvp); 215 } 216 } 217 218 builder.setParameters(parameters); 219 220 graphResult = getEntries(objType, builder); 221 } catch (Exception e) { 222 System.out.println(e.getMessage()); 223 } 224 225 return graphResult; 226 } 227 228 /** 229 * Get the entries based on the objType and builder URL 230 * 231 * @param objType 232 * @param builder 233 * @return 234 */ 235 private MSGraphResult getEntries(final OBJECT_TYPE objType, 236 final URIBuilder builder) { 237 MSGraphResult graphResult = new MSGraphResult(); 238 List<Entry> objList = new ArrayList<>(); 239 String nextLink = null; 240 241 try { 242 addVersionParameter(builder); 243 URI uri = builder.build(); 244 245 // System.out.println("GETTING: " + uri.toString()); 246 HttpGet request = new HttpGet(uri); 247 248 request.addHeader("Authorization", "Bearer " + accessToken); 249 HttpResponse response = httpclient.execute(request); 250 HttpEntity entity = response.getEntity(); 251 252 if (entity != null) { 253 String rawString = EntityUtils.toString(entity); 254 // System.out.println("RAW = " + rawString); 255 JSONObject json = new JSONObject(rawString); 256 257 if (json.has("odata.nextLink")) { 258 nextLink = json.getString("odata.nextLink"); 259 } 260 261 if (json.has("value")) { 262 JSONArray jArray = json.getJSONArray("value"); 263 264 if (jArray != null) { 265 for (int i = 0; i < jArray.length(); i++) { 266 Entry entry = getEntryFromJson(objType, jArray.getJSONObject(i)); 267 objList.add(entry); 268 } 269 } 270 } else { 271 Entry entry = getEntryFromJson(objType, json); 272 objList.add(entry); 273 } 274 } 275 276 graphResult = new MSGraphResult(objType, objList, nextLink); 277 } catch (Exception e) { 278 System.out.println(e.getMessage()); 279 } 280 281 return graphResult; 282 } 283 284 /** 285 * Get the next page of results 286 * 287 * @param nextResult 288 * @return 289 */ 290 public MSGraphResult nextPage(final MSGraphResult nextResult) { 291 MSGraphResult graphResult = new MSGraphResult(); 292 293 if (nextResult.nextLink == null) { 294 return new MSGraphResult(); 295 } 296 297 try { 298 299 URIBuilder builder = new URIBuilder(baseUrl + nextResult.nextLink); 300 301 graphResult = getEntries(nextResult.objectType, builder); 302 } catch (Exception e) { 303 System.out.println(e.getMessage()); 304 } 305 return graphResult; 306 } 307 308 private void addVersionParameter (URIBuilder builder) { 309 builder.addParameter("api-version", apiVersion); 310 } 311 312 /** 313 * Convert a json entry to an LDAP Entry. 314 * <p> 315 * Expects an objectId to be returned in the JSON. 316 * 317 * @param json 318 * @return 319 */ 320 private static Entry getEntryFromJson(final OBJECT_TYPE objectType, 321 final JSONObject json) { 322 323 Entry entry = null; 324 try { 325 Iterator<?> keys = json.keys(); 326 327 UUID objectId = UUID.fromString(json.getString("objectId")); 328 329 entry = new Entry("cn=" + objectId + ",cn=" + objectType.toString().toLowerCase()); 330 331 while (keys.hasNext()) { 332 String key = (String) keys.next(); 333 334 if (!key.startsWith("odata.") && !json.isNull(key)) { 335 Object objVal = json.get(key); 336 String value = null; 337 338 if (objVal instanceof JSONObject) { 339 value = json.toString(); 340 } else if (objVal instanceof JSONArray) { 341 342 } else if (objVal instanceof Boolean) { 343 value = new Boolean(json.getBoolean(key)).toString(); 344 } else { 345 value = json.getString(key); 346 } 347 348 if (value != null && !value.isEmpty()) { 349 entry.addAttribute(key, value); 350 } 351 } 352 } 353 } catch (JSONException je) { 354 je.printStackTrace(); 355 } 356 357 //System.out.println(entry.toLDIFString()); 358 return entry; 359 } 360}