source: trunk/grails-app/services/api/ApiService.groovy

Last change on this file was 2225, checked in by work@…, 9 years ago
  • changed application wide secret into a user specific api key which is automatically generated when a user is created. The key is available in the user's profile or through the user administration pages
File size: 8.8 KB
Line 
1/**
2 * ApiService Service
3 *
4 * Description of my service
5 *
6 * @author  Jeroen Wesbeek <work@osx.eu>
7 * @since       20120328
8 * @package     api
9 *
10 * Revision information:
11 * $Rev: 1430 $
12 * $Author: work@osx.eu $
13 * $Date: 2011-01-21 21:05:36 +0100 (Fri, 21 Jan 2011) $
14 */
15package api
16
17import java.security.MessageDigest
18import dbnp.studycapturing.Assay
19import dbnp.authentication.SecUser
20import org.springframework.context.ApplicationContextAware
21import org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib
22import org.springframework.context.ApplicationContext
23
24class ApiService implements Serializable, ApplicationContextAware {
25    // inject the module communication service
26    def moduleCommunicationService
27
28    // transactional
29    static transactional = false
30
31    // characters to split on when converting a string to camelCased format
32    static camelCaseSeparators = " |-|_"
33
34    // hasMany keys to ignore when flattening domain data
35    static ignoreHasManyKeys = [
36            "systemFields",
37            "templateBooleanFields",
38            "templateDateFields",
39            "templateDoubleFields",
40            "templateExtendableStringListFields",
41            "templateFileFields",
42            "templateLongFields",
43            "templateModuleFields",
44            "templateTermFields",
45            "templateRelTimeFields",
46            "templateStringFields",
47            "templateStringListFields",
48            "templateTemplateFields",
49            "templateTextFields"
50    ]
51
52    private ApplicationTagLib g
53
54    void setApplicationContext(ApplicationContext applicationContext) {
55        g = applicationContext.getBean(ApplicationTagLib)
56
57        // now you have a reference to g that you can call render() on
58    }
59
60    /**
61     * validate a client request by checking the validation checksum
62     * @param deviceID
63     * @param validation
64     * @return
65     */
66    def validateRequest(String deviceID, String validation) {
67        def validated = false
68
69        // disable validation check on development and ci
70        if (['development', 'ci'].contains(grails.util.GrailsUtil.environment)) {
71//            return true
72        }
73
74        // get token for this device ID
75        Token token = Token.findByDeviceID(deviceID)
76
77        // increase sequence
78        if (token) {
79            token.sequence = token.sequence+1
80            token.merge(flush: true)
81
82            // generate the validation checksum
83            MessageDigest digest = MessageDigest.getInstance("MD5")
84            String validationSum = new BigInteger(1,digest.digest("${token.deviceToken}${token.sequence}${token.user.apiKey}".getBytes())).toString(16).padLeft(32,"0")
85
86            // check if the validation confirms
87            validated = (validation == validationSum)
88        }
89
90        return validated
91    }
92
93    /**
94     * flatten domain data to relevant data to return in an api
95     * call and not to expose domain internals
96     *
97     * @param elements (List or Set)
98     * @return
99     */
100    def flattenDomainData(elements) {
101        def items = []
102
103        // iterate through elements
104        elements.each {
105            def fields  = (it.respondsTo('giveFields')) ? it.giveFields(): []
106            def item    = [:]
107
108            // check if element has a name
109            ['name','description'].each { checkName ->
110                if (it.hasProperty(checkName)) item[checkName] = it[checkName]
111            }
112
113            // add token
114            if (it.respondsTo('getToken')) {
115                // some domain methods implement getToken...
116                item['token'] = it.getToken()
117            } else if (it.respondsTo('giveUUID')) {
118                // ...while others implement giveUUID
119                item['token'] = it.giveUUID()
120            } else {
121                // and others don't at all, so far
122                // the consistency...
123                item['id'] = it.id
124            }
125
126            // add subject field values
127            fields.each { field ->
128                // get a camelCased version of the field name
129                def name = field.name.split(camelCaseSeparators).collect {it[0].toUpperCase() + it.substring(1)}.join('')
130                    name = name[0].toLowerCase() + name.substring(1)
131
132                // get the value for this field
133                def value = it.getFieldValue( field.name )
134
135                // add value
136                if (value.hasProperty('name')) {
137                    item[ name ] = value.name
138                } else {
139                    item[ name ] = value
140                }
141            }
142
143            // list hasMany sizes
144            it.properties.hasMany.each { hasManyItem ->
145                if (!ignoreHasManyKeys.contains(hasManyItem.key)) {
146                    // add count for this hasMany item
147                    item[ hasManyItem.key ] = it[ hasManyItem.key ].size()
148                }
149            }
150
151            // add item to resultset
152            items[ items.size() ] = item
153        }
154
155        return items
156    }
157
158    /**
159     * wrapper for performing api calls
160     *
161     * validates if the user may call this api
162     *
163     * @param params
164     * @param response
165     * @param itemName
166     * @param item
167     * @param block
168     */
169    def executeApiCall(params,response,itemName,item,block) {
170        // get variables from parameters
171        String deviceID     = (params.containsKey('deviceID')) ? params.deviceID : ''
172        String validation   = (params.containsKey('validation')) ? params.validation : ''
173
174        // fetch user based on deviceID
175        def user = Token.findByDeviceID(deviceID)?.user
176
177        // check if api call may be performed
178        if (!validateRequest(deviceID,validation)) {
179            // validation md5sum does not match predicted hash
180            response.sendError(401, "Unauthorized")
181        } else if (!item) {
182            // no results, invalid 'item'
183            response.sendError(400, "No such ${itemName}")
184        } else if (item.respondsTo('canRead') && !item.canRead(user)) {
185            // the user cannot read this data
186            response.sendError(401, "Unauthorized")
187        } else if (item.hasProperty('parent') && item.parent.respondsTo('canRead') && !item.parent.canRead(user)) {
188            // the user cannot read this data
189            response.sendError(401, "Unauthorized")
190        } else {
191            // allowed api call, execute block / closure
192            block()
193        }
194    }
195
196    /**
197     * get the measurement tokens from the remote module
198     *
199     * @param assay
200     * @param user
201     * @return
202     */
203    def getMeasurements(Assay assay, SecUser user) {
204        def serviceURL = "${assay.module.url}/rest/getMeasurements"
205        def serviceArguments = "assayToken=${assay.assayUUID}"
206        def json
207
208        // call module method
209        try {
210            json = moduleCommunicationService.callModuleMethod(
211                    assay.module.url,
212                    serviceURL,
213                    serviceArguments,
214                    "POST",
215                    user
216            );
217        } catch (Exception e) {
218            log.error "api.getMeasurements failed :: ${e.getMessage()}"
219            json = new org.codehaus.groovy.grails.web.json.JSONArray()
220        }
221
222        return json
223    }
224
225    /**
226     * get measurement data from the remote module in verbose format
227     *
228     * @param assay
229     * @param user
230     * @return
231     */
232    def getMeasurementData(Assay assay, SecUser user) {
233        def serviceURL = "${assay.module.url}/rest/getMeasurementData"
234        def serviceArguments = "assayToken=${assay.assayUUID}&verbose=true"
235        def json
236
237        // call module method
238        try {
239            json = moduleCommunicationService.callModuleMethod(
240                    assay.module.url,
241                    serviceURL,
242                    serviceArguments,
243                    "POST",
244                    user
245            );
246        } catch (Exception e) {
247            log.error "api.getMeasurementData failed :: ${e.getMessage()}"
248            json = new org.codehaus.groovy.grails.web.json.JSONArray()
249        }
250
251        return json
252    }
253
254    /**
255     * get the measurement meta data from the remote module
256     *
257     * @param assay
258     * @param user
259     * @return
260     */
261    def getMeasurementMetaData(Assay assay, SecUser user) {
262        def serviceURL = "${assay.module.url}/rest/getMeasurementMetaData"
263        def serviceArguments = "assayToken=${assay.assayUUID}"
264        def json
265
266        // call module method
267        try {
268            json = moduleCommunicationService.callModuleMethod(
269                    assay.module.url,
270                    serviceURL,
271                    serviceArguments,
272                    "POST",
273                    user
274            );
275        } catch (Exception e) {
276            log.error "api.getMeasurementMetaData failed :: ${e.getMessage()}"
277            json = new org.codehaus.groovy.grails.web.json.JSONArray()
278        }
279
280        return json
281    }
282}
Note: See TracBrowser for help on using the repository browser.