View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.userdirectory.brightspace.client;
23  
24  import org.opencastproject.userdirectory.brightspace.client.api.BrightspaceUser;
25  import org.opencastproject.userdirectory.brightspace.client.api.OrgUnitItem;
26  import org.opencastproject.userdirectory.brightspace.client.api.OrgUnitResponse;
27  import org.opencastproject.userdirectory.brightspace.client.api.PagingInfo;
28  import org.opencastproject.userdirectory.brightspace.client.api.UsersResponse;
29  
30  import com.fasterxml.jackson.databind.ObjectMapper;
31  
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import java.io.BufferedReader;
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.io.InputStreamReader;
39  import java.net.MalformedURLException;
40  import java.net.URI;
41  import java.net.URL;
42  import java.net.URLConnection;
43  import java.util.ArrayList;
44  import java.util.List;
45  import java.util.Set;
46  
47  import javax.net.ssl.HttpsURLConnection;
48  
49  import be.ugent.brightspace.idkeyauth.AuthenticationSecurityFactory;
50  import be.ugent.brightspace.idkeyauth.ID2LAppContext;
51  import be.ugent.brightspace.idkeyauth.ID2LUserContext;
52  
53  public class BrightspaceClientImpl implements BrightspaceClient {
54  
55    private static final Logger logger = LoggerFactory.getLogger(BrightspaceClientImpl.class);
56  
57    private static final String GET_USER_BY_USERNAME = "/d2l/api/lp/1.31/users/?UserName=";
58    private static final String GET_ALL_USERS = "/d2l/api/lp/1.31/users/";
59    private static final String GET_COURSES_BY_BRIGHTSPACE_USER_ID
60        = "/d2l/api/lp/1.31/enrollments/users/{brightspace-userid}/orgUnits/?orgUnitTypeId=3";
61    private static final String UNEXPECTED_JSON_RESPONSE = "The brightspace API returned a unexpected json response";
62    private static final String SUPER_ADMIN = "Super Administrator";
63    private static final String LTI_LEARNER_ROLE = "Learner";
64    private static final String LTI_INSTRUCTOR_ROLE = "Instructor";
65  
66    private final String url;
67    private final String applicationId;
68    private final String applicationKey;
69    private final String systemUserId;
70    private final String systemUserKey;
71    private final ID2LUserContext userContext;
72  
73    private final ObjectMapper objectMapper = new ObjectMapper();
74  
75    public BrightspaceClientImpl(String url, String applicationId, String applicationKey, String systemUserId,
76            String systemUserKey) {
77      this.url = url;
78      this.applicationId = applicationId;
79      this.applicationKey = applicationKey;
80      this.systemUserId = systemUserId;
81      this.systemUserKey = systemUserKey;
82      userContext = createUserContext();
83    }
84  
85    public BrightspaceUser findUser(String userName) throws BrightspaceClientException {
86      String request = GET_USER_BY_USERNAME + userName;
87  
88      try {
89        String response = httpGetRequest(request);
90        logger.debug(response);
91  
92        BrightspaceUser brightspaceUser = objectMapper
93                .readerFor(BrightspaceUser.class)
94                .readValue(response);
95        return brightspaceUser;
96      } catch (BrightspaceNotFoundException nfe) {
97        return null;
98      } catch (IOException e) {
99        logger.debug(e.toString());
100       throw new BrightspaceClientException(UNEXPECTED_JSON_RESPONSE, e);
101     }
102   }
103 
104   @Override public List<String> getRolesFromBrightspace(String userid, Set<String> instructorRoles)
105                             throws BrightspaceClientException {
106     logger.debug("Retrieving subscribed courses for user: {}", userid);
107 
108     boolean hasMoreItems;
109     String bookmark = null;
110     List<String> roleList = new ArrayList<>();
111     try {
112       do {
113         String request = composePagedUrl(bookmark, userid);
114         OrgUnitResponse orgUnitPage = findOrgUnitPage(request);
115         if (SUPER_ADMIN.equals(orgUnitPage.getItems().get(0).getRole().getName())) {
116           roleList.add("BRIGHTSPACE_ADMIN");
117           return roleList;
118         }
119 
120         for (OrgUnitItem course: orgUnitPage.getItems()) {
121           String brightspaceRole = course.getRole().getName();
122           String ltiRole = instructorRoles.contains(brightspaceRole) ? LTI_INSTRUCTOR_ROLE : LTI_LEARNER_ROLE;
123           String opencastRole = String.format("%s_%s", course.getOrgUnit().getId(), ltiRole);
124           roleList.add(opencastRole);
125         }
126 
127         PagingInfo pagingInfo = orgUnitPage.getPagingInfo();
128         hasMoreItems = pagingInfo.hasMoreItems();
129         bookmark = pagingInfo.getBookmark();
130 
131       } while (hasMoreItems);
132 
133       return roleList;
134     } catch (BrightspaceClientException e) {
135       logger.warn("Exception getting site/role membership for brightspace user {}", userid, e);
136       throw new BrightspaceClientException(UNEXPECTED_JSON_RESPONSE, e);
137     }
138 
139   }
140 
141   @Override
142   public List<BrightspaceUser> findAllUsers() throws BrightspaceClientException {
143     try {
144       String response = httpGetRequest(GET_ALL_USERS);
145       UsersResponse usersResponse = objectMapper.readValue(response, UsersResponse.class);
146       return usersResponse.getItems();
147     } catch (IOException e) {
148       throw new BrightspaceClientException(UNEXPECTED_JSON_RESPONSE, e);
149     } catch (BrightspaceNotFoundException nfe) {
150       throw new BrightspaceClientException(UNEXPECTED_JSON_RESPONSE, nfe);
151     }
152 
153   }
154 
155   public String getURL() {
156     return this.url;
157   }
158 
159   private String httpGetRequest(String request) throws BrightspaceClientException, BrightspaceNotFoundException {
160     URL url = createUrl(request);
161 
162     try {
163       HttpsURLConnection urlConnection = (HttpsURLConnection) getURLConnection(url);
164 
165       if (urlConnection.getResponseCode() == 404) {
166         logger.debug("Not found, 404 response");
167         throw new BrightspaceNotFoundException("not found");
168       }
169 
170       InputStream inputStream = urlConnection.getInputStream();
171       return readInputStream(inputStream);
172     } catch (IOException io) {
173       logger.warn("error in brightspace data fetching", io);
174       throw new BrightspaceClientException("could not read response");
175     }
176   }
177 
178   private URL createUrl(String request) throws BrightspaceClientException {
179     URI uri = userContext.createAuthenticatedUri(request, "GET");
180     URL url;
181     try {
182       url = uri.toURL();
183     } catch (MalformedURLException mue) {
184       throw new BrightspaceClientException("url was malformed", mue);
185     }
186     logger.debug("about to make GET request to : {}", uri);
187     return url;
188   }
189 
190   private ID2LUserContext createUserContext() {
191     ID2LAppContext securityContext = AuthenticationSecurityFactory
192             .createSecurityContext(applicationId, applicationKey, url);
193     return securityContext.createUserContext(systemUserId, systemUserKey);
194   }
195 
196   private URLConnection getURLConnection(URL url) throws BrightspaceClientException {
197     try {
198       HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
199       urlConnection.setRequestMethod("GET");
200       urlConnection.setRequestProperty("Content-Type", "application/json");
201       urlConnection.setRequestProperty("Accept-Charset", "utf-8");
202       urlConnection.setDoInput(true);
203       urlConnection.setDoOutput(true);
204       return urlConnection;
205     } catch (IOException ioe) {
206       throw new BrightspaceClientException("Brightspace api unreachable", ioe);
207     }
208   }
209 
210   private String readInputStream(InputStream inputStream) throws BrightspaceClientException {
211     StringBuilder content = new StringBuilder();
212     try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
213       String inputline;
214       while ((inputline = bufferedReader.readLine()) != null) {
215         content.append(inputline);
216       }
217       logger.debug("call to brightspace api: {}", content);
218       return content.toString();
219     } catch (IOException io) {
220       throw new BrightspaceClientException("Could not read response", io);
221     }
222 
223   }
224 
225   private OrgUnitResponse findOrgUnitPage(String request) throws BrightspaceClientException {
226     try {
227       String response = httpGetRequest(request);
228       return objectMapper.readValue(response, OrgUnitResponse.class);
229     } catch (IOException e) {
230       logger.error(UNEXPECTED_JSON_RESPONSE);
231       throw new BrightspaceClientException(UNEXPECTED_JSON_RESPONSE, e);
232     } catch (BrightspaceNotFoundException nfe) {
233       logger.error(UNEXPECTED_JSON_RESPONSE);
234       throw new BrightspaceClientException(UNEXPECTED_JSON_RESPONSE, nfe);
235     }
236   }
237 
238   private String composePagedUrl(String bookmark, String brightspaceUserId) {
239     String request = GET_COURSES_BY_BRIGHTSPACE_USER_ID.replaceAll("\\{\\S+}", brightspaceUserId);
240     if (bookmark != null) {
241       request += "&bookmark=" + bookmark;
242     }
243     return request;
244   }
245 }