1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.external.endpoint;
22
23 import static com.entwinemedia.fn.data.json.Jsons.f;
24 import static com.entwinemedia.fn.data.json.Jsons.obj;
25 import static org.apache.commons.lang3.StringUtils.isBlank;
26 import static org.apache.commons.lang3.StringUtils.isNotBlank;
27 import static org.opencastproject.util.DateTimeSupport.fromUTC;
28 import static org.opencastproject.util.DateTimeSupport.toUTC;
29 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
30
31 import org.opencastproject.external.common.ApiMediaType;
32 import org.opencastproject.external.common.ApiResponseBuilder;
33 import org.opencastproject.security.urlsigning.exception.UrlSigningException;
34 import org.opencastproject.security.urlsigning.service.UrlSigningService;
35 import org.opencastproject.util.DateTimeSupport;
36 import org.opencastproject.util.OsgiUtil;
37 import org.opencastproject.util.RestUtil.R;
38 import org.opencastproject.util.doc.rest.RestParameter;
39 import org.opencastproject.util.doc.rest.RestQuery;
40 import org.opencastproject.util.doc.rest.RestResponse;
41 import org.opencastproject.util.doc.rest.RestService;
42
43 import com.entwinemedia.fn.data.Opt;
44
45 import org.joda.time.DateTime;
46 import org.joda.time.DateTimeConstants;
47 import org.osgi.service.cm.ConfigurationException;
48 import org.osgi.service.cm.ManagedService;
49 import org.osgi.service.component.annotations.Activate;
50 import org.osgi.service.component.annotations.Component;
51 import org.osgi.service.component.annotations.Reference;
52 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
55
56 import java.text.ParseException;
57 import java.util.Date;
58 import java.util.Dictionary;
59
60 import javax.servlet.http.HttpServletResponse;
61 import javax.ws.rs.FormParam;
62 import javax.ws.rs.HeaderParam;
63 import javax.ws.rs.POST;
64 import javax.ws.rs.Path;
65 import javax.ws.rs.Produces;
66 import javax.ws.rs.core.Response;
67
68 @Path("/api/security")
69 @Produces({ ApiMediaType.JSON, ApiMediaType.VERSION_1_0_0, ApiMediaType.VERSION_1_1_0, ApiMediaType.VERSION_1_2_0,
70 ApiMediaType.VERSION_1_3_0, ApiMediaType.VERSION_1_4_0, ApiMediaType.VERSION_1_5_0,
71 ApiMediaType.VERSION_1_6_0, ApiMediaType.VERSION_1_7_0, ApiMediaType.VERSION_1_8_0,
72 ApiMediaType.VERSION_1_9_0, ApiMediaType.VERSION_1_10_0, ApiMediaType.VERSION_1_11_0 })
73 @RestService(name = "externalapisecurity", title = "External API Security Service", notes = {}, abstractText = "Provides security operations related to the external API")
74 @Component(
75 immediate = true,
76 service = { SecurityEndpoint.class,ManagedService.class },
77 property = {
78 "service.description=External API - Security Endpoint",
79 "opencast.service.type=org.opencastproject.external.security",
80 "opencast.service.path=/api/security"
81 }
82 )
83 @JaxrsResource
84 public class SecurityEndpoint implements ManagedService {
85
86 protected static final String URL_SIGNING_EXPIRES_DURATION_SECONDS_KEY = "url.signing.expires.seconds";
87
88
89 protected static final long DEFAULT_URL_SIGNING_EXPIRE_DURATION = 2 * 60 * 60;
90
91
92 private static final Logger log = LoggerFactory.getLogger(SecurityEndpoint.class);
93
94 private long expireSeconds = DEFAULT_URL_SIGNING_EXPIRE_DURATION;
95
96
97 private UrlSigningService urlSigningService;
98
99
100 @Reference
101 void setUrlSigningService(UrlSigningService urlSigningService) {
102 this.urlSigningService = urlSigningService;
103 }
104
105
106 @Activate
107 void activate() {
108 log.info("Activating External API - Security Endpoint");
109 }
110
111 @Override
112 public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
113 if (properties == null) {
114 log.info("No configuration available, using defaults");
115 return;
116 }
117
118 Opt<Long> expiration = OsgiUtil.getOptCfg(properties, URL_SIGNING_EXPIRES_DURATION_SECONDS_KEY).toOpt()
119 .map(com.entwinemedia.fn.fns.Strings.toLongF);
120 if (expiration.isSome()) {
121 expireSeconds = expiration.get();
122 log.info("The property {} has been configured to expire signed URLs in {}.",
123 URL_SIGNING_EXPIRES_DURATION_SECONDS_KEY, DateTimeSupport.humanReadableTime(expireSeconds));
124 } else {
125 expireSeconds = DEFAULT_URL_SIGNING_EXPIRE_DURATION;
126 log.info("The property {} has not been configured, so the default is being used to expire signed URLs in {}.",
127 URL_SIGNING_EXPIRES_DURATION_SECONDS_KEY, DateTimeSupport.humanReadableTime(expireSeconds));
128 }
129 }
130
131 @POST
132 @Path("sign")
133 @RestQuery(name = "signurl", description = "Returns a signed URL that can be played back for the indicated period of time, while access is optionally restricted to the specified IP address.", returnDescription = "", restParameters = {
134 @RestParameter(name = "url", isRequired = true, description = "The linke to encode.", type = STRING),
135 @RestParameter(name = "valid-until", description = "Until when is the signed url valid", isRequired = false, type = STRING),
136 @RestParameter(name = "valid-source", description = "The IP address from which the url can be accessed", isRequired = false, type = STRING) }, responses = {
137 @RestResponse(description = "The signed URL is returned.", responseCode = HttpServletResponse.SC_OK),
138 @RestResponse(description = "The caller is not authorized to have the link signed.", responseCode = HttpServletResponse.SC_UNAUTHORIZED) })
139 public Response signUrl(@HeaderParam("Accept") String acceptHeader, @FormParam("url") String url,
140 @FormParam("valid-until") String validUntilUtc, @FormParam("valid-source") String validSource) {
141 if (isBlank(url))
142 return R.badRequest("Query parameter 'url' is mandatory");
143
144 final DateTime validUntil;
145 if (isNotBlank(validUntilUtc)) {
146 try {
147 validUntil = new DateTime(fromUTC(validUntilUtc));
148 } catch (IllegalStateException | ParseException e) {
149 return R.badRequest("Query parameter 'valid-until' is not a valid ISO-8601 date string");
150 }
151 } else {
152 validUntil = new DateTime(new Date().getTime() + expireSeconds * DateTimeConstants.MILLIS_PER_SECOND);
153 }
154
155 if (urlSigningService.accepts(url)) {
156 String signedUrl = "";
157 try {
158 signedUrl = urlSigningService.sign(url, validUntil, null, validSource);
159 } catch (UrlSigningException e) {
160 log.warn("Error while trying to sign url '{}':", url, e);
161 return ApiResponseBuilder.Json.ok(acceptHeader, obj(f("error", "Error while signing url")));
162 }
163 return ApiResponseBuilder.Json.ok(acceptHeader, obj(f("url", signedUrl), f("valid-until", toUTC(validUntil.getMillis()))));
164 } else {
165 return ApiResponseBuilder.Json.ok(acceptHeader, obj(f("error", "Given URL cannot be signed")));
166 }
167 }
168 }